mirror of
https://github.com/bmad-code-org/BMAD-METHOD.git
synced 2026-01-30 04:32:02 +00:00
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:
@@ -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:
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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
|
|
||||||
|
|||||||
@@ -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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user