mirror of
https://github.com/bmadcode/BMAD-METHOD.git
synced 2025-12-29 16:14:59 +00:00
custom inst imporove
This commit is contained in:
@@ -198,8 +198,8 @@ module.exports = {
|
||||
}
|
||||
} else {
|
||||
// Discover agents from custom location
|
||||
const customAgentLocation = config.custom_agent_location
|
||||
? resolvePath(config.custom_agent_location, config)
|
||||
const customAgentLocation = config.custom_stand_alone_location
|
||||
? resolvePath(config.custom_stand_alone_location, config)
|
||||
: path.join(config.bmadFolder, 'custom', 'src', 'agents');
|
||||
|
||||
console.log(chalk.dim(`Searching for agents in: ${customAgentLocation}\n`));
|
||||
|
||||
@@ -236,9 +236,31 @@ class ConfigCollector {
|
||||
}
|
||||
this.collectedConfig[moduleName] = { ...this.existingConfig[moduleName] };
|
||||
|
||||
// Special handling for user_name: ensure it has a value
|
||||
if (
|
||||
moduleName === 'core' &&
|
||||
(!this.collectedConfig[moduleName].user_name || this.collectedConfig[moduleName].user_name === '[USER_NAME]')
|
||||
) {
|
||||
this.collectedConfig[moduleName].user_name = this.getDefaultUsername();
|
||||
}
|
||||
|
||||
// Also populate allAnswers for cross-referencing
|
||||
for (const [key, value] of Object.entries(this.existingConfig[moduleName])) {
|
||||
this.allAnswers[`${moduleName}_${key}`] = value;
|
||||
// Ensure user_name is properly set in allAnswers too
|
||||
let finalValue = value;
|
||||
if (moduleName === 'core' && key === 'user_name' && (!value || value === '[USER_NAME]')) {
|
||||
finalValue = this.getDefaultUsername();
|
||||
}
|
||||
this.allAnswers[`${moduleName}_${key}`] = finalValue;
|
||||
}
|
||||
} else if (moduleName === 'core') {
|
||||
// No existing core config - ensure we at least have user_name
|
||||
if (!this.collectedConfig[moduleName]) {
|
||||
this.collectedConfig[moduleName] = {};
|
||||
}
|
||||
if (!this.collectedConfig[moduleName].user_name) {
|
||||
this.collectedConfig[moduleName].user_name = this.getDefaultUsername();
|
||||
this.allAnswers[`${moduleName}_user_name`] = this.getDefaultUsername();
|
||||
}
|
||||
}
|
||||
// Show "no config" message for modules with no new questions
|
||||
|
||||
@@ -38,7 +38,6 @@ const { CLIUtils } = require('../../../lib/cli-utils');
|
||||
const { ManifestGenerator } = require('./manifest-generator');
|
||||
const { IdeConfigManager } = require('./ide-config-manager');
|
||||
const { replaceAgentSidecarFolders } = require('./post-install-sidecar-replacement');
|
||||
const { CustomHandler } = require('../custom/handler');
|
||||
|
||||
class Installer {
|
||||
constructor() {
|
||||
@@ -52,7 +51,6 @@ class Installer {
|
||||
this.dependencyResolver = new DependencyResolver();
|
||||
this.configCollector = new ConfigCollector();
|
||||
this.ideConfigManager = new IdeConfigManager();
|
||||
this.customHandler = new CustomHandler();
|
||||
this.installedFiles = []; // Track all installed files
|
||||
this.ttsInjectedFiles = []; // Track files with TTS injection applied
|
||||
}
|
||||
@@ -914,6 +912,7 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||
await this.moduleManager.runModuleInstaller('core', bmadDir, {
|
||||
installedIDEs: config.ides || [],
|
||||
moduleConfig: moduleConfigs.core || {},
|
||||
coreConfig: moduleConfigs.core || {},
|
||||
logger: {
|
||||
log: (msg) => console.log(msg),
|
||||
error: (msg) => console.error(msg),
|
||||
@@ -931,6 +930,7 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||
await this.moduleManager.runModuleInstaller(moduleName, bmadDir, {
|
||||
installedIDEs: config.ides || [],
|
||||
moduleConfig: moduleConfigs[moduleName] || {},
|
||||
coreConfig: moduleConfigs.core || {},
|
||||
logger: {
|
||||
log: (msg) => console.log(msg),
|
||||
error: (msg) => console.error(msg),
|
||||
@@ -1028,9 +1028,6 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||
}
|
||||
}
|
||||
|
||||
// Update custom content (add new files, don't remove existing)
|
||||
await this.updateCustomContent(projectDir, bmadDir);
|
||||
|
||||
// Replace {agent_sidecar_folder} placeholders in all agent files
|
||||
console.log(chalk.dim('\n Configuring agent sidecar folders...'));
|
||||
const sidecarResults = await replaceAgentSidecarFolders(bmadDir);
|
||||
@@ -1168,133 +1165,6 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
/**
|
||||
* Update custom content (add new files without removing existing ones)
|
||||
* @param {string} projectDir - Project directory
|
||||
* @param {string} bmadDir - BMAD installation directory
|
||||
*/
|
||||
async updateCustomContent(projectDir, bmadDir) {
|
||||
try {
|
||||
// Find all custom content
|
||||
const customContents = await this.customHandler.findCustomContent(projectDir);
|
||||
|
||||
if (customContents.length === 0) {
|
||||
return; // No custom content to update
|
||||
}
|
||||
|
||||
// Load core config
|
||||
const coreConfigPath = path.join(bmadDir, 'bmb', 'config.yaml');
|
||||
let coreConfig = {};
|
||||
if (await fs.pathExists(coreConfigPath)) {
|
||||
const yamlLib = require('yaml');
|
||||
const coreConfigContent = await fs.readFile(coreConfigPath, 'utf8');
|
||||
coreConfig = yamlLib.load(coreConfigContent);
|
||||
}
|
||||
|
||||
console.log(chalk.dim('\nUpdating custom content...'));
|
||||
|
||||
for (const customPath of customContents) {
|
||||
const customInfo = await this.customHandler.getCustomInfo(customPath);
|
||||
if (customInfo) {
|
||||
console.log(chalk.dim(` Checking: ${customInfo.name}`));
|
||||
|
||||
// Install only adds new files, doesn't remove existing ones
|
||||
const results = await this.customHandler.install(
|
||||
customPath,
|
||||
bmadDir,
|
||||
{
|
||||
user_name: coreConfig.user_name,
|
||||
communication_language: coreConfig.communication_language,
|
||||
output_folder: coreConfig.output_folder,
|
||||
bmad_folder: path.basename(bmadDir),
|
||||
},
|
||||
(filePath) => {
|
||||
this.installedFiles.push(filePath);
|
||||
},
|
||||
);
|
||||
|
||||
// Only show if new files were added or preserved
|
||||
if (results.filesCopied > 0 || results.preserved > 0) {
|
||||
if (results.filesCopied > 0) {
|
||||
console.log(chalk.dim(` Added ${results.filesCopied} new file(s)`));
|
||||
}
|
||||
if (results.preserved > 0) {
|
||||
console.log(chalk.dim(` Preserved ${results.preserved} existing file(s)`));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(chalk.yellow(`Warning: Failed to update custom content: ${error.message}`));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Install custom content by ID
|
||||
* @param {string} customId - Custom content ID
|
||||
* @param {string} bmadDir - BMAD installation directory
|
||||
* @param {Object} coreConfig - Core configuration
|
||||
*/
|
||||
async installCustomContent(customId, bmadDir, coreConfig) {
|
||||
try {
|
||||
// Find the custom content
|
||||
const customContents = await this.customHandler.findCustomContent(process.cwd());
|
||||
let customInfo = null;
|
||||
let customPath = null;
|
||||
|
||||
for (const path of customContents) {
|
||||
const info = await this.customHandler.getCustomInfo(path);
|
||||
if (info && info.id === customId) {
|
||||
customInfo = info;
|
||||
customPath = path;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!customInfo || !customPath) {
|
||||
console.warn(chalk.yellow(`Warning: Custom content '${customId}' not found`));
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(chalk.dim(` Installing: ${customInfo.name}`));
|
||||
|
||||
// Install the custom content
|
||||
const results = await this.customHandler.install(
|
||||
customPath,
|
||||
bmadDir,
|
||||
{
|
||||
user_name: coreConfig.user_name,
|
||||
communication_language: coreConfig.communication_language,
|
||||
output_folder: coreConfig.output_folder,
|
||||
bmad_folder: path.basename(bmadDir),
|
||||
},
|
||||
(filePath) => {
|
||||
this.installedFiles.push(filePath);
|
||||
},
|
||||
);
|
||||
|
||||
// Show results
|
||||
if (results.agentsInstalled > 0) {
|
||||
console.log(chalk.dim(` ${results.agentsInstalled} agent(s) installed`));
|
||||
}
|
||||
if (results.workflowsInstalled > 0) {
|
||||
console.log(chalk.dim(` ${results.workflowsInstalled} workflow(s) installed`));
|
||||
}
|
||||
if (results.filesCopied > 0) {
|
||||
console.log(chalk.dim(` ${results.filesCopied} file(s) copied`));
|
||||
}
|
||||
if (results.preserved > 0) {
|
||||
console.log(chalk.dim(` ${results.preserved} file(s) preserved`));
|
||||
}
|
||||
if (results.errors.length > 0) {
|
||||
console.log(chalk.yellow(` ${results.errors.length} error(s)`));
|
||||
for (const error of results.errors) console.log(chalk.dim(` - ${error}`));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(chalk.red(`Failed to install custom content '${customId}':`, error.message));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Private: Create directory structure
|
||||
*/
|
||||
|
||||
@@ -121,8 +121,14 @@ class ModuleManager {
|
||||
for (const entry of entries) {
|
||||
const fullPath = path.join(dir, entry.name);
|
||||
|
||||
// Skip hidden directories and node_modules
|
||||
if (entry.name.startsWith('.') || entry.name === 'node_modules' || entry.name === 'dist' || entry.name === 'build') {
|
||||
// Skip hidden directories, node_modules, and literal placeholder directories
|
||||
if (
|
||||
entry.name.startsWith('.') ||
|
||||
entry.name === 'node_modules' ||
|
||||
entry.name === 'dist' ||
|
||||
entry.name === 'build' ||
|
||||
entry.name === '{project-root}'
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -139,9 +145,14 @@ class ModuleManager {
|
||||
|
||||
// Check if this directory contains a module (install-config.yaml OR custom.yaml)
|
||||
const installerConfigPath = path.join(fullPath, '_module-installer', 'install-config.yaml');
|
||||
const customConfigPath = path.join(fullPath, 'custom.yaml');
|
||||
const customConfigPath = path.join(fullPath, '_module-installer', 'custom.yaml');
|
||||
const rootCustomConfigPath = path.join(fullPath, 'custom.yaml');
|
||||
|
||||
if ((await fs.pathExists(installerConfigPath)) || (await fs.pathExists(customConfigPath))) {
|
||||
if (
|
||||
(await fs.pathExists(installerConfigPath)) ||
|
||||
(await fs.pathExists(customConfigPath)) ||
|
||||
(await fs.pathExists(rootCustomConfigPath))
|
||||
) {
|
||||
modulePaths.add(fullPath);
|
||||
// Don't scan inside modules - they might have their own nested structures
|
||||
continue;
|
||||
@@ -176,11 +187,12 @@ class ModuleManager {
|
||||
for (const entry of entries) {
|
||||
if (entry.isDirectory()) {
|
||||
const modulePath = path.join(this.modulesSourcePath, entry.name);
|
||||
// Check for module structure (only install-config.yaml is valid now)
|
||||
// Check for module structure (install-config.yaml OR custom.yaml)
|
||||
const installerConfigPath = path.join(modulePath, '_module-installer', 'install-config.yaml');
|
||||
const customConfigPath = path.join(modulePath, '_module-installer', 'custom.yaml');
|
||||
|
||||
// Skip if this doesn't look like a module
|
||||
if (!(await fs.pathExists(installerConfigPath))) {
|
||||
if (!(await fs.pathExists(installerConfigPath)) && !(await fs.pathExists(customConfigPath))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -228,13 +240,16 @@ class ModuleManager {
|
||||
async getModuleInfo(modulePath, defaultName, sourceDescription) {
|
||||
// Check for module structure (install-config.yaml OR custom.yaml)
|
||||
const installerConfigPath = path.join(modulePath, '_module-installer', 'install-config.yaml');
|
||||
const customConfigPath = path.join(modulePath, 'custom.yaml');
|
||||
const customConfigPath = path.join(modulePath, '_module-installer', 'custom.yaml');
|
||||
const rootCustomConfigPath = path.join(modulePath, 'custom.yaml');
|
||||
let configPath = null;
|
||||
|
||||
if (await fs.pathExists(installerConfigPath)) {
|
||||
configPath = installerConfigPath;
|
||||
} else if (await fs.pathExists(customConfigPath)) {
|
||||
configPath = customConfigPath;
|
||||
} else if (await fs.pathExists(rootCustomConfigPath)) {
|
||||
configPath = rootCustomConfigPath;
|
||||
}
|
||||
|
||||
// Skip if this doesn't look like a module
|
||||
@@ -242,6 +257,8 @@ class ModuleManager {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Mark as custom if it's using custom.yaml OR if it's outside src/modules
|
||||
const isCustomSource = sourceDescription !== 'src/modules';
|
||||
const moduleInfo = {
|
||||
id: defaultName,
|
||||
path: modulePath,
|
||||
@@ -252,7 +269,7 @@ class ModuleManager {
|
||||
description: 'BMAD Module',
|
||||
version: '5.0.0',
|
||||
source: sourceDescription,
|
||||
isCustom: configPath === customConfigPath,
|
||||
isCustom: configPath === customConfigPath || configPath === rootCustomConfigPath || isCustomSource,
|
||||
};
|
||||
|
||||
// Read module config for metadata
|
||||
@@ -295,8 +312,8 @@ class ModuleManager {
|
||||
return srcModulePath;
|
||||
}
|
||||
|
||||
// Also check for custom.yaml in src/modules
|
||||
const customConfigPath = path.join(srcModulePath, 'custom.yaml');
|
||||
// Also check for custom.yaml in src/modules/_module-installer
|
||||
const customConfigPath = path.join(srcModulePath, '_module-installer', 'custom.yaml');
|
||||
if (await fs.pathExists(customConfigPath)) {
|
||||
return srcModulePath;
|
||||
}
|
||||
@@ -314,13 +331,16 @@ class ModuleManager {
|
||||
// Need to read configs to match by ID
|
||||
for (const modulePath of allModulePaths) {
|
||||
const installerConfigPath = path.join(modulePath, '_module-installer', 'install-config.yaml');
|
||||
const customConfigPath = path.join(modulePath, 'custom.yaml');
|
||||
const customConfigPath = path.join(modulePath, '_module-installer', 'custom.yaml');
|
||||
const rootCustomConfigPath = path.join(modulePath, 'custom.yaml');
|
||||
|
||||
let configPath = null;
|
||||
if (await fs.pathExists(installerConfigPath)) {
|
||||
configPath = installerConfigPath;
|
||||
} else if (await fs.pathExists(customConfigPath)) {
|
||||
configPath = customConfigPath;
|
||||
} else if (await fs.pathExists(rootCustomConfigPath)) {
|
||||
configPath = rootCustomConfigPath;
|
||||
}
|
||||
|
||||
if (configPath) {
|
||||
@@ -532,7 +552,8 @@ class ModuleManager {
|
||||
}
|
||||
|
||||
// Skip config.yaml templates - we'll generate clean ones with actual values
|
||||
if (file === 'config.yaml' || file.endsWith('/config.yaml')) {
|
||||
// But allow custom.yaml which is used for custom modules
|
||||
if ((file === 'config.yaml' || file.endsWith('/config.yaml')) && !file.endsWith('custom.yaml')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1059,6 +1080,7 @@ class ModuleManager {
|
||||
const result = await moduleInstaller.install({
|
||||
projectRoot,
|
||||
config: options.moduleConfig || {},
|
||||
coreConfig: options.coreConfig || {},
|
||||
installedIDEs: options.installedIDEs || [],
|
||||
logger,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user