installation for remote modules now indicates its getching or installing so it does not appear to be hung when caching the remote in the local npm cache

This commit is contained in:
Brian Madison
2026-01-18 08:11:35 -06:00
parent 966ca5db0b
commit 28e6dded4d
6 changed files with 152 additions and 122 deletions

View File

@@ -1,7 +1,6 @@
code: bmb code: bmb
name: "BMB: BMad Builder - Agent, Workflow and Module Builder" name: "BMad Builder (BoMB!)"
header: "BMad Optimized Builder (BoMB) Module Configuration" description: "Agent, Workflow and Module Builder"
subheader: "Configure the settings for the BoMB Factory!\nThe agent, workflow and module builder for BMad™ "
default_selected: false # This module will not be selected by default for new installations default_selected: false # This module will not be selected by default for new installations
# Variables from Core Config inserted: # Variables from Core Config inserted:

View File

@@ -1,7 +1,6 @@
code: bmm code: bmm
name: "BMM: BMad Method Agile-AI Driven-Development" name: "BMad Method Agile-AI Driven-Development"
header: "BMad Method™: Breakthrough Method of Agile-Ai Driven-Dev" description: "AI-driven agile development framework"
subheader: "Agent and Workflow Configuration for this module"
default_selected: true # This module will be selected by default for new installations default_selected: true # This module will be selected by default for new installations
# Variables from Core Config inserted: # Variables from Core Config inserted:

View File

@@ -6,28 +6,25 @@ modules:
url: https://github.com/bmad-code-org/bmad-module-creative-intelligence-suite url: https://github.com/bmad-code-org/bmad-module-creative-intelligence-suite
module-definition: src/module.yaml module-definition: src/module.yaml
code: cis code: cis
name: "CIS: Creative Innovation Suite" name: "Creative Innovation Suite"
header: "CIS: Creative Innovation Suite" description: "Creative tools for writing, brainstorming, and more"
subheader: "Unleash your creativity with the BMad CIS!"
description: ""
defaultSelected: false defaultSelected: false
type: bmad-org
bmad-game-dev-studio: bmad-game-dev-studio:
url: https://github.com/bmad-code-org/bmad-module-game-dev-studio.git url: https://github.com/bmad-code-org/bmad-module-game-dev-studio.git
module-definition: src/module.yaml module-definition: src/module.yaml
code: BGDS code: BGDS
name: "BGDS: BMad Game Dev Suite" name: "BMad Game Dev Suite"
header: "BGDS: BMad Game Dev Suite" description: "Game development agents and workflows"
subheader: "Explore and create the groundwork for your game ideas with the BMad Game Dev suite!"
description: "Very similar to the BMad Method - but a focus on the slightly different needs of Game Development - with multiple platforms and game type specifics included to explore"
defaultSelected: false defaultSelected: false
type: bmad-org
# bmad-whiteport-design-system: whiteport-design-system:
# url: https://github.com/bmad-code-org/bmad-method-wds-expansion url: https://github.com/bmad-code-org/bmad-method-wds-expansion
# module-definition: src/module.yaml module-definition: src/module.yaml
# code: WDS code: WDS
# name: "WDS: Whiteport UX Design System" name: "Whiteport UX Design System"
# header: "WDS: Whiteport UX Design System" description: "UX design framework with Figma integration"
# subheader: "Professional Designer UX Design Module Expansion to the BMad MEthod" defaultSelected: false
# description: "Experienced UX Designers can leverage the WDS with or without the BMad Method to harness their existing skills and tools (such as figma) while also utilizing an industry leading design framework." type: community
# defaultSelected: false

View File

@@ -53,6 +53,7 @@ class ExternalModuleManager {
subheader: moduleConfig.subheader, subheader: moduleConfig.subheader,
description: moduleConfig.description || '', description: moduleConfig.description || '',
defaultSelected: moduleConfig.defaultSelected === true, defaultSelected: moduleConfig.defaultSelected === true,
type: moduleConfig.type || 'community', // bmad-org or community
isExternal: true, isExternal: true,
}); });
} }
@@ -93,6 +94,7 @@ class ExternalModuleManager {
subheader: moduleConfig.subheader, subheader: moduleConfig.subheader,
description: moduleConfig.description || '', description: moduleConfig.description || '',
defaultSelected: moduleConfig.defaultSelected === true, defaultSelected: moduleConfig.defaultSelected === true,
type: moduleConfig.type || 'community', // bmad-org or community
isExternal: true, isExternal: true,
}; };
} }

View File

@@ -422,7 +422,7 @@ class ModuleManager {
// Check if already cloned // Check if already cloned
if (await fs.pathExists(moduleCacheDir)) { if (await fs.pathExists(moduleCacheDir)) {
// Try to update if it's a git repo // Try to update if it's a git repo
const updateSpinner = ora(`Updating ${moduleInfo.name} from remote repository...`).start(); const fetchSpinner = ora(`Fetching ${moduleInfo.name}...`).start();
try { try {
const currentRef = execSync('git rev-parse HEAD', { cwd: moduleCacheDir, stdio: 'pipe' }).toString().trim(); const currentRef = execSync('git rev-parse HEAD', { cwd: moduleCacheDir, stdio: 'pipe' }).toString().trim();
execSync('git fetch --depth 1', { cwd: moduleCacheDir, stdio: 'pipe' }); execSync('git fetch --depth 1', { cwd: moduleCacheDir, stdio: 'pipe' });
@@ -430,15 +430,13 @@ class ModuleManager {
execSync('git pull --ff-only', { cwd: moduleCacheDir, stdio: 'pipe' }); execSync('git pull --ff-only', { cwd: moduleCacheDir, stdio: 'pipe' });
const newRef = execSync('git rev-parse HEAD', { cwd: moduleCacheDir, stdio: 'pipe' }).toString().trim(); const newRef = execSync('git rev-parse HEAD', { cwd: moduleCacheDir, stdio: 'pipe' }).toString().trim();
if (currentRef === newRef) { fetchSpinner.succeed(`Fetched ${moduleInfo.name}`);
updateSpinner.succeed(`${moduleInfo.name} is already up to date`); // Force dependency install if we got new code
} else { if (currentRef !== newRef) {
updateSpinner.succeed(`Updated ${moduleInfo.name} to latest version`);
// Force dependency install since we got new code
needsDependencyInstall = true; needsDependencyInstall = true;
} }
} catch { } catch {
updateSpinner.warn(`Update failed, re-downloading ${moduleInfo.name}`); fetchSpinner.warn(`Fetch failed, re-downloading ${moduleInfo.name}`);
// If update fails, remove and re-clone // If update fails, remove and re-clone
await fs.remove(moduleCacheDir); await fs.remove(moduleCacheDir);
wasNewClone = true; wasNewClone = true;
@@ -449,14 +447,14 @@ class ModuleManager {
// Clone if not exists or was removed // Clone if not exists or was removed
if (wasNewClone) { if (wasNewClone) {
const cloneSpinner = ora(`Downloading ${moduleInfo.name} from remote repository...`).start(); const fetchSpinner = ora(`Fetching ${moduleInfo.name}...`).start();
try { try {
execSync(`git clone --depth 1 "${moduleInfo.url}" "${moduleCacheDir}"`, { execSync(`git clone --depth 1 "${moduleInfo.url}" "${moduleCacheDir}"`, {
stdio: 'pipe', stdio: 'pipe',
}); });
cloneSpinner.succeed(`Downloaded ${moduleInfo.name}`); fetchSpinner.succeed(`Fetched ${moduleInfo.name}`);
} catch (error) { } catch (error) {
cloneSpinner.fail(`Failed to download ${moduleInfo.name}`); fetchSpinner.fail(`Failed to fetch ${moduleInfo.name}`);
throw new Error(`Failed to clone external module '${moduleCode}': ${error.message}`); throw new Error(`Failed to clone external module '${moduleCode}': ${error.message}`);
} }
} }

View File

@@ -253,51 +253,8 @@ class UI {
console.log(chalk.dim(` Found existing modules: ${[...installedModuleIds].join(', ')}`)); console.log(chalk.dim(` Found existing modules: ${[...installedModuleIds].join(', ')}`));
// Ask about BMad Method Module (bmm) // Unified module selection - all modules in one grouped multiselect
const wantsBmm = await prompts.confirm({ let selectedModules = await this.selectAllModules(installedModuleIds);
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,
});
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 // After module selection, ask about custom modules
console.log(''); console.log('');
@@ -352,36 +309,10 @@ class UI {
// This section is only for new installations (update returns early above) // This section is only for new installations (update returns early above)
const { installedModuleIds } = await this.getExistingInstallation(confirmedDirectory); const { installedModuleIds } = await this.getExistingInstallation(confirmedDirectory);
// Ask about BMad Method Module (this repo) // Unified module selection - all modules in one grouped multiselect
const wantsBmm = await prompts.confirm({ let selectedModules = await this.selectAllModules(installedModuleIds);
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: true,
});
// Ask about BMad Builder Module // Ask about custom content (local modules/agents/workflows)
const wantsBmg = await prompts.confirm({
message: 'Select the BMad Builder Module for installation?\n ---> Create Your Own Custom BMad Agents, Workflows and Modules',
default: false,
});
let selectedOfficialModules = [];
if (wantsBmm) {
selectedOfficialModules.push('bmm');
}
const wantsExternalModules = await prompts.confirm({
message: 'Would you like to choose any other Recommended BMad Core Modules for installation?\n',
default: true,
});
let selectedExternalModules = [];
if (wantsExternalModules) {
const externalModuleChoices = await this.getExternalModuleChoices();
selectedExternalModules = await this.selectExternalModules(externalModuleChoices);
}
// Ask about custom content
const wantsCustomContent = await prompts.confirm({ const wantsCustomContent = await prompts.confirm({
message: 'Would you like to install a locally stored custom module (this includes custom agents and workflows also)?', message: 'Would you like to install a locally stored custom module (this includes custom agents and workflows also)?',
default: false, default: false,
@@ -391,19 +322,9 @@ class UI {
customContentConfig = await this.promptCustomContentSource(); customContentConfig = await this.promptCustomContentSource();
} }
// Store the selected modules for later
customContentConfig._selectedOfficialModules = selectedOfficialModules;
customContentConfig._selectedExternalModules = selectedExternalModules;
// Build the final list of selected modules
let selectedModules = [
...(customContentConfig._selectedOfficialModules || []),
...(customContentConfig._selectedExternalModules || []),
];
// Add custom content modules if any were selected // Add custom content modules if any were selected
if (customContentConfig && customContentConfig.selectedModuleIds) { if (customContentConfig && customContentConfig.selectedModuleIds) {
selectedModules = [...selectedModules, ...customContentConfig.selectedModuleIds]; selectedModules.push(...customContentConfig.selectedModuleIds);
} }
selectedModules = selectedModules.filter((m) => m !== 'core'); selectedModules = selectedModules.filter((m) => m !== 'core');
@@ -513,7 +434,7 @@ class UI {
let selectedIdes = []; let selectedIdes = [];
selectedIdes = await prompts.groupMultiselect({ selectedIdes = await prompts.groupMultiselect({
message: `Select tools to configure ${chalk.dim('(↑/↓ navigates multiselect, SPACE toggles, A to toggles All, ENTER confirm)')}:`, message: `Select tools to configure ${chalk.dim('(↑/↓ navigates, SPACE toggles, ENTER to confirm)')}:`,
options: groupedOptions, options: groupedOptions,
initialValues: initialValues.length > 0 ? initialValues : undefined, initialValues: initialValues.length > 0 ? initialValues : undefined,
required: true, required: true,
@@ -663,6 +584,7 @@ class UI {
value: `__CUSTOM_CONTENT__${customFile}`, // Unique value for each custom content value: `__CUSTOM_CONTENT__${customFile}`, // Unique value for each custom content
checked: true, // Default to selected since user chose to provide custom content checked: true, // Default to selected since user chose to provide custom content
path: customInfo.path, // Track path to avoid duplicates path: customInfo.path, // Track path to avoid duplicates
hint: customInfo.description || undefined,
}); });
} }
} }
@@ -689,6 +611,7 @@ class UI {
name: `${chalk.cyan('✓')} ${mod.name} ${chalk.gray(`(cached)`)}`, name: `${chalk.cyan('✓')} ${mod.name} ${chalk.gray(`(cached)`)}`,
value: mod.id, value: mod.id,
checked: isNewInstallation ? mod.defaultSelected || false : installedModuleIds.has(mod.id), checked: isNewInstallation ? mod.defaultSelected || false : installedModuleIds.has(mod.id),
hint: mod.description || undefined,
}); });
} }
} }
@@ -710,6 +633,7 @@ class UI {
name: mod.name, name: mod.name,
value: mod.id, value: mod.id,
checked: isNewInstallation ? mod.defaultSelected || false : installedModuleIds.has(mod.id), checked: isNewInstallation ? mod.defaultSelected || false : installedModuleIds.has(mod.id),
hint: mod.description || undefined,
}); });
} }
} }
@@ -741,7 +665,7 @@ class UI {
]; ];
const selected = await prompts.multiselect({ const selected = await prompts.multiselect({
message: `Select modules to install ${chalk.dim('(↑/↓ navigates multiselect, SPACE toggles, A to toggles All, ENTER confirm)')}:`, message: `Select modules to install ${chalk.dim('(↑/↓ navigates, SPACE toggles, ENTER to confirm)')}:`,
choices: choicesWithSkipOption, choices: choicesWithSkipOption,
required: true, required: true,
}); });
@@ -770,6 +694,7 @@ class UI {
name: mod.name, name: mod.name,
value: mod.code, // Use the code (e.g., 'cis') as the value value: mod.code, // Use the code (e.g., 'cis') as the value
checked: mod.defaultSelected || false, checked: mod.defaultSelected || false,
hint: mod.description || undefined, // Show description as hint
module: mod, // Store full module info for later use module: mod, // Store full module info for later use
})); }));
} }
@@ -783,7 +708,7 @@ class UI {
async selectExternalModules(externalModuleChoices, defaultSelections = []) { async selectExternalModules(externalModuleChoices, defaultSelections = []) {
// Build a message showing available modules // Build a message showing available modules
const availableNames = externalModuleChoices.map((c) => c.name).join(', '); 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)')}:`; const message = `Select official BMad modules to install ${chalk.dim('(↑/↓ navigates, SPACE toggles, ENTER to confirm)')}:`;
// Mark choices as checked based on defaultSelections // Mark choices as checked based on defaultSelections
const choicesWithDefaults = externalModuleChoices.map((choice) => ({ const choicesWithDefaults = externalModuleChoices.map((choice) => ({
@@ -819,6 +744,116 @@ class UI {
return selected ? selected.filter((m) => m !== '__NONE__') : []; return selected ? selected.filter((m) => m !== '__NONE__') : [];
} }
/**
* Select all modules (core + official + community) using grouped multiselect
* @param {Set} installedModuleIds - Currently installed module IDs
* @returns {Array} Selected module codes
*/
async selectAllModules(installedModuleIds = new Set()) {
const { ModuleManager } = require('../installers/lib/modules/manager');
const moduleManager = new ModuleManager();
const { modules: localModules } = await moduleManager.listAvailable();
// Get external modules
const externalManager = new ExternalModuleManager();
const externalModules = await externalManager.listAvailable();
// Build grouped options
const groupedOptions = {};
const initialValues = [];
// Helper to build module entry with proper sorting and selection
const buildModuleEntry = (mod, value) => {
const isInstalled = installedModuleIds.has(value);
const isDefault = mod.defaultSelected === true;
return {
label: mod.description ? `${mod.name}${mod.description}` : mod.name,
value,
// For sorting: defaultSelected=0, others=1
sortKey: isDefault ? 0 : 1,
// Pre-select if default selected OR already installed
selected: isDefault || isInstalled,
};
};
// Group 1: BMad Core (BMM, BMB)
const coreModules = [];
for (const mod of localModules) {
if (!mod.isCustom && (mod.id === 'bmm' || mod.id === 'bmb')) {
const entry = buildModuleEntry(mod, mod.id);
coreModules.push(entry);
if (entry.selected) {
initialValues.push(mod.id);
}
}
}
// Sort: defaultSelected first, then others
coreModules.sort((a, b) => a.sortKey - b.sortKey);
// Remove sortKey from final entries
if (coreModules.length > 0) {
groupedOptions['BMad Core'] = coreModules.map(({ label, value }) => ({ label, value }));
}
// Group 2: BMad Official Modules (type: bmad-org)
const officialModules = [];
for (const mod of externalModules) {
if (mod.type === 'bmad-org') {
const entry = buildModuleEntry(mod, mod.code);
officialModules.push(entry);
if (entry.selected) {
initialValues.push(mod.code);
}
}
}
officialModules.sort((a, b) => a.sortKey - b.sortKey);
if (officialModules.length > 0) {
groupedOptions['BMad Official Modules'] = officialModules.map(({ label, value }) => ({ label, value }));
}
// Group 3: Community Modules (type: community)
const communityModules = [];
for (const mod of externalModules) {
if (mod.type === 'community') {
const entry = buildModuleEntry(mod, mod.code);
communityModules.push(entry);
if (entry.selected) {
initialValues.push(mod.code);
}
}
}
communityModules.sort((a, b) => a.sortKey - b.sortKey);
if (communityModules.length > 0) {
groupedOptions['Community Modules'] = communityModules.map(({ label, value }) => ({ label, value }));
}
// Add "None" option at the end
groupedOptions[' '] = [
{
label: '⚠ None - Skip module installation',
value: '__NONE__',
},
];
const selected = await prompts.groupMultiselect({
message: `Select modules to install ${chalk.dim('(↑/↓ navigates, SPACE toggles, ENTER to confirm)')}:`,
options: groupedOptions,
initialValues: initialValues.length > 0 ? initialValues : undefined,
required: true,
selectableGroups: false,
});
// If user selected both "__NONE__" and other items, honor the "None" choice
if (selected && selected.includes('__NONE__') && selected.length > 1) {
console.log();
console.log(chalk.yellow('⚠️ "None" was selected, so no modules will be installed.'));
console.log();
return [];
}
// Filter out the special '__NONE__' value
return selected ? selected.filter((m) => m !== '__NONE__') : [];
}
/** /**
* Prompt for directory selection * Prompt for directory selection
* @returns {Object} Directory answer from prompt * @returns {Object} Directory answer from prompt
@@ -1372,7 +1407,7 @@ class UI {
]; ];
const keepModules = await prompts.multiselect({ const keepModules = await prompts.multiselect({
message: `Select custom modules to keep ${chalk.dim('(↑/↓ navigates multiselect, SPACE toggles, A to toggles All, ENTER confirm)')}:`, message: `Select custom modules to keep ${chalk.dim('(↑/↓ navigates, SPACE toggles, ENTER to confirm)')}:`,
choices: choicesWithSkip, choices: choicesWithSkip,
required: true, required: true,
}); });