mirror of
https://github.com/bmadcode/BMAD-METHOD.git
synced 2025-12-29 16:14:59 +00:00
The install directory is now configurable, with a few minute issues
This commit is contained in:
@@ -56,10 +56,7 @@ module.exports = {
|
||||
// Check if installation succeeded
|
||||
if (result && result.success) {
|
||||
console.log(chalk.green('\n✨ Installation complete!'));
|
||||
console.log(
|
||||
chalk.cyan('BMAD Core and Selected Modules have been installed to:'),
|
||||
chalk.bold(result.path || path.resolve(config.directory, 'bmad')),
|
||||
);
|
||||
console.log(chalk.cyan('BMAD Core and Selected Modules have been installed to:'), chalk.bold(result.path));
|
||||
console.log(chalk.yellow('\nThank you for helping test the early release version of the new BMad Core and BMad Method!'));
|
||||
console.log(chalk.cyan('Stable Beta coming soon - please read the full readme.md and linked documentation to get started!'));
|
||||
process.exit(0);
|
||||
|
||||
@@ -18,9 +18,15 @@ class ConfigCollector {
|
||||
* @param {string} projectDir - Target project directory
|
||||
*/
|
||||
async loadExistingConfig(projectDir) {
|
||||
const bmadDir = path.join(projectDir, 'bmad');
|
||||
this.existingConfig = {};
|
||||
|
||||
// Check if project directory exists first
|
||||
if (!(await fs.pathExists(projectDir))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const bmadDir = path.join(projectDir, 'bmad');
|
||||
|
||||
// Check if bmad directory exists
|
||||
if (!(await fs.pathExists(bmadDir))) {
|
||||
return false;
|
||||
@@ -173,7 +179,7 @@ class ConfigCollector {
|
||||
const questions = [];
|
||||
for (const key of newKeys) {
|
||||
const item = moduleConfig[key];
|
||||
const question = await this.buildQuestion(moduleName, key, item);
|
||||
const question = await this.buildQuestion(moduleName, key, item, moduleConfig);
|
||||
if (question) {
|
||||
questions.push(question);
|
||||
}
|
||||
@@ -343,7 +349,7 @@ class ConfigCollector {
|
||||
continue;
|
||||
}
|
||||
|
||||
const question = await this.buildQuestion(moduleName, key, item);
|
||||
const question = await this.buildQuestion(moduleName, key, item, moduleConfig);
|
||||
if (question) {
|
||||
questions.push(question);
|
||||
}
|
||||
@@ -457,13 +463,61 @@ class ConfigCollector {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace placeholders in a string with collected config values
|
||||
* @param {string} str - String with placeholders
|
||||
* @param {string} currentModule - Current module name (to look up defaults in same module)
|
||||
* @param {Object} moduleConfig - Current module's config schema (to look up defaults)
|
||||
* @returns {string} String with placeholders replaced
|
||||
*/
|
||||
replacePlaceholders(str, currentModule = null, moduleConfig = null) {
|
||||
if (typeof str !== 'string') {
|
||||
return str;
|
||||
}
|
||||
|
||||
return str.replaceAll(/{([^}]+)}/g, (match, configKey) => {
|
||||
// Preserve special placeholders
|
||||
if (configKey === 'project-root' || configKey === 'value' || configKey === 'directory_name') {
|
||||
return match;
|
||||
}
|
||||
|
||||
// Look for the config value in allAnswers (already answered questions)
|
||||
let configValue = this.allAnswers[configKey] || this.allAnswers[`core_${configKey}`];
|
||||
|
||||
// Check in already collected config
|
||||
if (!configValue) {
|
||||
for (const mod of Object.keys(this.collectedConfig)) {
|
||||
if (mod !== '_meta' && this.collectedConfig[mod] && this.collectedConfig[mod][configKey]) {
|
||||
configValue = this.collectedConfig[mod][configKey];
|
||||
// Remove {project-root}/ prefix if present for cleaner display
|
||||
if (typeof configValue === 'string' && configValue.includes('{project-root}/')) {
|
||||
configValue = configValue.replace('{project-root}/', '');
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If still not found and we're in the same module, use the default from the config schema
|
||||
if (!configValue && currentModule && moduleConfig && moduleConfig[configKey]) {
|
||||
const referencedItem = moduleConfig[configKey];
|
||||
if (referencedItem && referencedItem.default !== undefined) {
|
||||
configValue = referencedItem.default;
|
||||
}
|
||||
}
|
||||
|
||||
return configValue || match;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Build an inquirer question from a config item
|
||||
* @param {string} moduleName - Module name
|
||||
* @param {string} key - Config key
|
||||
* @param {Object} item - Config item definition
|
||||
* @param {Object} moduleConfig - Full module config schema (for resolving defaults)
|
||||
*/
|
||||
async buildQuestion(moduleName, key, item) {
|
||||
async buildQuestion(moduleName, key, item, moduleConfig = null) {
|
||||
const questionName = `${moduleName}_${key}`;
|
||||
|
||||
// Check for existing value
|
||||
@@ -483,9 +537,42 @@ class ConfigCollector {
|
||||
let defaultValue = item.default;
|
||||
let choices = null;
|
||||
|
||||
if (typeof defaultValue === 'string' && defaultValue.includes('{directory_name}') && this.currentProjectDir) {
|
||||
const dirName = path.basename(this.currentProjectDir);
|
||||
defaultValue = defaultValue.replaceAll('{directory_name}', dirName);
|
||||
// Check if default contains references to other fields in the same module
|
||||
const hasSameModuleReference = typeof defaultValue === 'string' && defaultValue.match(/{([^}]+)}/);
|
||||
let dynamicDefault = false;
|
||||
|
||||
// Replace placeholders in default value with collected config values
|
||||
if (typeof defaultValue === 'string') {
|
||||
if (defaultValue.includes('{directory_name}') && this.currentProjectDir) {
|
||||
const dirName = path.basename(this.currentProjectDir);
|
||||
defaultValue = defaultValue.replaceAll('{directory_name}', dirName);
|
||||
}
|
||||
|
||||
// Check if this references another field in the same module (for dynamic defaults)
|
||||
if (hasSameModuleReference && moduleConfig) {
|
||||
const matches = defaultValue.match(/{([^}]+)}/g);
|
||||
if (matches) {
|
||||
for (const match of matches) {
|
||||
const fieldName = match.slice(1, -1); // Remove { }
|
||||
// Check if this field exists in the same module config
|
||||
if (moduleConfig[fieldName]) {
|
||||
dynamicDefault = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If not dynamic, replace placeholders now
|
||||
if (!dynamicDefault) {
|
||||
defaultValue = this.replacePlaceholders(defaultValue, moduleName, moduleConfig);
|
||||
}
|
||||
|
||||
// Strip {project-root}/ from defaults since it will be added back by result template
|
||||
// This makes the display cleaner and user input simpler
|
||||
if (defaultValue.includes('{project-root}/')) {
|
||||
defaultValue = defaultValue.replace('{project-root}/', '');
|
||||
}
|
||||
}
|
||||
|
||||
// Handle different question types
|
||||
@@ -548,31 +635,67 @@ class ConfigCollector {
|
||||
message = item.prompt;
|
||||
}
|
||||
|
||||
// Replace placeholders in prompt message with collected config values
|
||||
if (typeof message === 'string') {
|
||||
message = this.replacePlaceholders(message, moduleName, moduleConfig);
|
||||
}
|
||||
|
||||
// Add current value indicator for existing configs
|
||||
if (existingValue !== null && existingValue !== undefined) {
|
||||
if (typeof existingValue === 'boolean') {
|
||||
message += chalk.dim(` (current: ${existingValue ? 'true' : 'false'})`);
|
||||
defaultValue = existingValue;
|
||||
} else if (Array.isArray(existingValue)) {
|
||||
message += chalk.dim(` (current: ${existingValue.join(', ')})`);
|
||||
} else if (questionType !== 'list') {
|
||||
// Show the cleaned value (without {project-root}/) for display
|
||||
message += chalk.dim(` (current: ${existingValue})`);
|
||||
defaultValue = existingValue;
|
||||
}
|
||||
} else if (item.example && questionType === 'input') {
|
||||
// Show example for input fields
|
||||
const exampleText = typeof item.example === 'string' ? item.example.replace('{project-root}/', '') : JSON.stringify(item.example);
|
||||
let exampleText = typeof item.example === 'string' ? item.example : JSON.stringify(item.example);
|
||||
// Replace placeholders in example
|
||||
if (typeof exampleText === 'string') {
|
||||
exampleText = this.replacePlaceholders(exampleText, moduleName, moduleConfig);
|
||||
exampleText = exampleText.replace('{project-root}/', '');
|
||||
}
|
||||
message += chalk.dim(` (e.g., ${exampleText})`);
|
||||
}
|
||||
|
||||
// Build the question object
|
||||
const question = {
|
||||
type: questionType,
|
||||
name: questionName,
|
||||
message: message,
|
||||
default: defaultValue,
|
||||
};
|
||||
|
||||
// Set default - if it's dynamic, use a function that inquirer will evaluate with current answers
|
||||
// But if we have an existing value, always use that instead
|
||||
if (existingValue !== null && existingValue !== undefined && questionType !== 'list') {
|
||||
question.default = existingValue;
|
||||
} else if (dynamicDefault && typeof item.default === 'string') {
|
||||
const originalDefault = item.default;
|
||||
question.default = (answers) => {
|
||||
// Replace placeholders using answers from previous questions in the same batch
|
||||
let resolved = originalDefault;
|
||||
resolved = resolved.replaceAll(/{([^}]+)}/g, (match, fieldName) => {
|
||||
// Look for the answer in the current batch (prefixed with module name)
|
||||
const answerKey = `${moduleName}_${fieldName}`;
|
||||
if (answers[answerKey] !== undefined) {
|
||||
return answers[answerKey];
|
||||
}
|
||||
// Fall back to collected config
|
||||
return this.collectedConfig[moduleName]?.[fieldName] || match;
|
||||
});
|
||||
// Strip {project-root}/ for cleaner display
|
||||
if (resolved.includes('{project-root}/')) {
|
||||
resolved = resolved.replace('{project-root}/', '');
|
||||
}
|
||||
return resolved;
|
||||
};
|
||||
} else {
|
||||
question.default = defaultValue;
|
||||
}
|
||||
|
||||
// Add choices for select types
|
||||
if (choices) {
|
||||
question.choices = choices;
|
||||
@@ -584,6 +707,23 @@ class ConfigCollector {
|
||||
if (!input && item.required) {
|
||||
return 'This field is required';
|
||||
}
|
||||
// Validate against regex pattern if provided
|
||||
if (input && item.regex) {
|
||||
const regex = new RegExp(item.regex);
|
||||
if (!regex.test(input)) {
|
||||
return `Invalid format. Must match pattern: ${item.regex}`;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
// Add validation for checkbox (multi-select) fields
|
||||
if (questionType === 'checkbox' && item.required) {
|
||||
question.validate = (answers) => {
|
||||
if (!answers || answers.length === 0) {
|
||||
return 'At least one option must be selected';
|
||||
}
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -33,6 +33,85 @@ class Installer {
|
||||
this.installedFiles = []; // Track all installed files
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the bmad installation directory in a project
|
||||
* Checks for custom bmad_folder names (.bmad, .bmad-custom, etc.) and falls back to 'bmad'
|
||||
* @param {string} projectDir - Project directory
|
||||
* @returns {Promise<string>} Path to bmad directory
|
||||
*/
|
||||
async findBmadDir(projectDir) {
|
||||
// Check if project directory exists
|
||||
if (!(await fs.pathExists(projectDir))) {
|
||||
// Project doesn't exist yet, return default
|
||||
return path.join(projectDir, 'bmad');
|
||||
}
|
||||
|
||||
// First, try to read from existing core config to get the bmad_folder value
|
||||
const possibleDirs = ['.bmad', 'bmad']; // Common defaults
|
||||
|
||||
// Check if any of these exist
|
||||
for (const dir of possibleDirs) {
|
||||
const fullPath = path.join(projectDir, dir);
|
||||
if (await fs.pathExists(fullPath)) {
|
||||
// Try to read the config to confirm this is a bmad installation
|
||||
const configPath = path.join(fullPath, 'core', 'config.yaml');
|
||||
if (await fs.pathExists(configPath)) {
|
||||
return fullPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If nothing found, check for any directory that contains core/config.yaml
|
||||
// This handles custom bmad_folder names
|
||||
const entries = await fs.readdir(projectDir, { withFileTypes: true });
|
||||
for (const entry of entries) {
|
||||
if (entry.isDirectory()) {
|
||||
const configPath = path.join(projectDir, entry.name, 'core', 'config.yaml');
|
||||
if (await fs.pathExists(configPath)) {
|
||||
return path.join(projectDir, entry.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Default fallback
|
||||
return path.join(projectDir, 'bmad');
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy a file and replace {bmad_folder} placeholder with actual folder name
|
||||
* @param {string} sourcePath - Source file path
|
||||
* @param {string} targetPath - Target file path
|
||||
* @param {string} bmadFolderName - The bmad folder name to use for replacement
|
||||
*/
|
||||
async copyFileWithPlaceholderReplacement(sourcePath, targetPath, bmadFolderName) {
|
||||
// List of text file extensions that should have placeholder replacement
|
||||
const textExtensions = ['.md', '.yaml', '.yml', '.txt', '.json', '.js', '.ts', '.html', '.css', '.sh', '.bat', '.csv'];
|
||||
const ext = path.extname(sourcePath).toLowerCase();
|
||||
|
||||
// Check if this is a text file that might contain placeholders
|
||||
if (textExtensions.includes(ext)) {
|
||||
try {
|
||||
// Read the file content
|
||||
let content = await fs.readFile(sourcePath, 'utf8');
|
||||
|
||||
// Replace {bmad_folder} placeholder with actual folder name
|
||||
if (content.includes('{bmad_folder}')) {
|
||||
content = content.replaceAll('{bmad_folder}', bmadFolderName);
|
||||
}
|
||||
|
||||
// Write to target with replaced content
|
||||
await fs.ensureDir(path.dirname(targetPath));
|
||||
await fs.writeFile(targetPath, content, 'utf8');
|
||||
} catch {
|
||||
// If reading as text fails (might be binary despite extension), fall back to regular copy
|
||||
await fs.copy(sourcePath, targetPath, { overwrite: true });
|
||||
}
|
||||
} else {
|
||||
// Binary file or other file type - just copy directly
|
||||
await fs.copy(sourcePath, targetPath, { overwrite: true });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect Tool/IDE configurations after module configuration
|
||||
* @param {string} projectDir - Project directory
|
||||
@@ -61,7 +140,7 @@ class Installer {
|
||||
// Check for already configured IDEs
|
||||
const { Detector } = require('./detector');
|
||||
const detector = new Detector();
|
||||
const bmadDir = path.join(projectDir, 'bmad');
|
||||
const bmadDir = path.join(projectDir, this.bmadFolderName || 'bmad');
|
||||
|
||||
// During full reinstall, use the saved previous IDEs since bmad dir was deleted
|
||||
// Otherwise detect from existing installation
|
||||
@@ -197,6 +276,10 @@ class Installer {
|
||||
moduleConfigs = await this.configCollector.collectAllConfigurations(config.modules || [], path.resolve(config.directory));
|
||||
}
|
||||
|
||||
// Get bmad_folder from config (default to 'bmad' for backwards compatibility)
|
||||
const bmadFolderName = moduleConfigs.core && moduleConfigs.core.bmad_folder ? moduleConfigs.core.bmad_folder : 'bmad';
|
||||
this.bmadFolderName = bmadFolderName; // Store for use in other methods
|
||||
|
||||
// Tool selection will be collected after we determine if it's a reinstall/update/new install
|
||||
|
||||
const spinner = ora('Preparing installation...').start();
|
||||
@@ -205,6 +288,63 @@ class Installer {
|
||||
// Resolve target directory (path.resolve handles platform differences)
|
||||
const projectDir = path.resolve(config.directory);
|
||||
|
||||
// Check if bmad_folder has changed from existing installation (only if project dir exists)
|
||||
let existingBmadDir = null;
|
||||
let existingBmadFolderName = null;
|
||||
|
||||
if (await fs.pathExists(projectDir)) {
|
||||
existingBmadDir = await this.findBmadDir(projectDir);
|
||||
existingBmadFolderName = path.basename(existingBmadDir);
|
||||
}
|
||||
|
||||
const targetBmadDir = path.join(projectDir, bmadFolderName);
|
||||
|
||||
// If bmad_folder changed during update/upgrade, back up old folder and do fresh install
|
||||
if (existingBmadDir && (await fs.pathExists(existingBmadDir)) && existingBmadFolderName !== bmadFolderName) {
|
||||
spinner.stop();
|
||||
console.log(chalk.yellow(`\n⚠️ bmad_folder has changed: ${existingBmadFolderName} → ${bmadFolderName}`));
|
||||
console.log(chalk.yellow('This will result in a fresh installation to the new folder.'));
|
||||
|
||||
const inquirer = require('inquirer');
|
||||
const { confirmFreshInstall } = await inquirer.prompt([
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'confirmFreshInstall',
|
||||
message: chalk.cyan('Proceed with fresh install? (Your old folder will be backed up)'),
|
||||
default: true,
|
||||
},
|
||||
]);
|
||||
|
||||
if (!confirmFreshInstall) {
|
||||
console.log(chalk.yellow('Installation cancelled.'));
|
||||
return { success: false, cancelled: true };
|
||||
}
|
||||
|
||||
spinner.start('Backing up existing installation...');
|
||||
|
||||
// Find a unique backup name
|
||||
let backupDir = `${existingBmadDir}-bak`;
|
||||
let counter = 1;
|
||||
while (await fs.pathExists(backupDir)) {
|
||||
backupDir = `${existingBmadDir}-bak-${counter}`;
|
||||
counter++;
|
||||
}
|
||||
|
||||
// Rename the old folder to backup
|
||||
await fs.move(existingBmadDir, backupDir);
|
||||
|
||||
spinner.succeed(`Backed up ${existingBmadFolderName} → ${path.basename(backupDir)}`);
|
||||
console.log(chalk.cyan('\n📋 Important:'));
|
||||
console.log(chalk.dim(` - Your old installation has been backed up to: ${path.basename(backupDir)}`));
|
||||
console.log(chalk.dim(` - If you had custom agents or configurations, copy them from:`));
|
||||
console.log(chalk.dim(` ${path.basename(backupDir)}/_cfg/`));
|
||||
console.log(chalk.dim(` - To the new location:`));
|
||||
console.log(chalk.dim(` ${bmadFolderName}/_cfg/`));
|
||||
console.log('');
|
||||
|
||||
spinner.start('Starting fresh installation...');
|
||||
}
|
||||
|
||||
// Create a project directory if it doesn't exist (user already confirmed)
|
||||
if (!(await fs.pathExists(projectDir))) {
|
||||
spinner.text = 'Creating installation directory...';
|
||||
@@ -225,7 +365,7 @@ class Installer {
|
||||
}
|
||||
}
|
||||
|
||||
const bmadDir = path.join(projectDir, 'bmad');
|
||||
const bmadDir = path.join(projectDir, bmadFolderName);
|
||||
|
||||
// Check existing installation
|
||||
spinner.text = 'Checking for existing installation...';
|
||||
@@ -726,7 +866,8 @@ class Installer {
|
||||
const spinner = ora('Checking installation...').start();
|
||||
|
||||
try {
|
||||
const bmadDir = path.join(path.resolve(config.directory), 'bmad');
|
||||
const projectDir = path.resolve(config.directory);
|
||||
const bmadDir = await this.findBmadDir(projectDir);
|
||||
const existingInstall = await this.detector.detect(bmadDir);
|
||||
|
||||
if (!existingInstall.installed) {
|
||||
@@ -786,7 +927,8 @@ class Installer {
|
||||
* Get installation status
|
||||
*/
|
||||
async getStatus(directory) {
|
||||
const bmadDir = path.join(path.resolve(directory), 'bmad');
|
||||
const projectDir = path.resolve(directory);
|
||||
const bmadDir = await this.findBmadDir(projectDir);
|
||||
return await this.detector.detect(bmadDir);
|
||||
}
|
||||
|
||||
@@ -801,14 +943,15 @@ class Installer {
|
||||
* Uninstall BMAD
|
||||
*/
|
||||
async uninstall(directory) {
|
||||
const bmadDir = path.join(path.resolve(directory), 'bmad');
|
||||
const projectDir = path.resolve(directory);
|
||||
const bmadDir = await this.findBmadDir(projectDir);
|
||||
|
||||
if (await fs.pathExists(bmadDir)) {
|
||||
await fs.remove(bmadDir);
|
||||
}
|
||||
|
||||
// Clean up IDE configurations
|
||||
await this.ideManager.cleanup(path.resolve(directory));
|
||||
await this.ideManager.cleanup(projectDir);
|
||||
|
||||
return { success: true };
|
||||
}
|
||||
@@ -986,7 +1129,7 @@ class Installer {
|
||||
const targetPath = path.join(agentsDir, fileName);
|
||||
|
||||
if (await fs.pathExists(sourcePath)) {
|
||||
await fs.copy(sourcePath, targetPath);
|
||||
await this.copyFileWithPlaceholderReplacement(sourcePath, targetPath, this.bmadFolderName || 'bmad');
|
||||
this.installedFiles.push(targetPath);
|
||||
}
|
||||
}
|
||||
@@ -1002,7 +1145,7 @@ class Installer {
|
||||
const targetPath = path.join(tasksDir, fileName);
|
||||
|
||||
if (await fs.pathExists(sourcePath)) {
|
||||
await fs.copy(sourcePath, targetPath);
|
||||
await this.copyFileWithPlaceholderReplacement(sourcePath, targetPath, this.bmadFolderName || 'bmad');
|
||||
this.installedFiles.push(targetPath);
|
||||
}
|
||||
}
|
||||
@@ -1018,7 +1161,7 @@ class Installer {
|
||||
const targetPath = path.join(toolsDir, fileName);
|
||||
|
||||
if (await fs.pathExists(sourcePath)) {
|
||||
await fs.copy(sourcePath, targetPath);
|
||||
await this.copyFileWithPlaceholderReplacement(sourcePath, targetPath, this.bmadFolderName || 'bmad');
|
||||
this.installedFiles.push(targetPath);
|
||||
}
|
||||
}
|
||||
@@ -1034,7 +1177,7 @@ class Installer {
|
||||
const targetPath = path.join(templatesDir, fileName);
|
||||
|
||||
if (await fs.pathExists(sourcePath)) {
|
||||
await fs.copy(sourcePath, targetPath);
|
||||
await this.copyFileWithPlaceholderReplacement(sourcePath, targetPath, this.bmadFolderName || 'bmad');
|
||||
this.installedFiles.push(targetPath);
|
||||
}
|
||||
}
|
||||
@@ -1049,7 +1192,7 @@ class Installer {
|
||||
await fs.ensureDir(path.dirname(targetPath));
|
||||
|
||||
if (await fs.pathExists(dataPath)) {
|
||||
await fs.copy(dataPath, targetPath);
|
||||
await this.copyFileWithPlaceholderReplacement(dataPath, targetPath, this.bmadFolderName || 'bmad');
|
||||
this.installedFiles.push(targetPath);
|
||||
}
|
||||
}
|
||||
@@ -1109,9 +1252,8 @@ class Installer {
|
||||
}
|
||||
}
|
||||
|
||||
// Copy the file
|
||||
await fs.ensureDir(path.dirname(targetFile));
|
||||
await fs.copy(sourceFile, targetFile, { overwrite: true });
|
||||
// Copy the file with placeholder replacement
|
||||
await this.copyFileWithPlaceholderReplacement(sourceFile, targetFile, this.bmadFolderName || 'bmad');
|
||||
|
||||
// Track the installed file
|
||||
this.installedFiles.push(targetFile);
|
||||
@@ -1182,7 +1324,7 @@ class Installer {
|
||||
if (!(await fs.pathExists(customizePath))) {
|
||||
const genericTemplatePath = getSourcePath('utility', 'templates', 'agent.customize.template.yaml');
|
||||
if (await fs.pathExists(genericTemplatePath)) {
|
||||
await fs.copy(genericTemplatePath, customizePath);
|
||||
await this.copyFileWithPlaceholderReplacement(genericTemplatePath, customizePath, this.bmadFolderName || 'bmad');
|
||||
console.log(chalk.dim(` Created customize: ${moduleName}-${agentName}.customize.yaml`));
|
||||
}
|
||||
}
|
||||
@@ -1417,7 +1559,7 @@ class Installer {
|
||||
|
||||
try {
|
||||
const projectDir = path.resolve(config.directory);
|
||||
const bmadDir = path.join(projectDir, 'bmad');
|
||||
const bmadDir = await this.findBmadDir(projectDir);
|
||||
|
||||
// Check if bmad directory exists
|
||||
if (!(await fs.pathExists(bmadDir))) {
|
||||
@@ -1548,7 +1690,7 @@ class Installer {
|
||||
|
||||
try {
|
||||
const projectDir = path.resolve(config.directory);
|
||||
const bmadDir = path.join(projectDir, 'bmad');
|
||||
const bmadDir = await this.findBmadDir(projectDir);
|
||||
|
||||
// Check if bmad directory exists
|
||||
if (!(await fs.pathExists(bmadDir))) {
|
||||
@@ -2097,7 +2239,7 @@ class Installer {
|
||||
const targetDocPath = path.join(docsDir, `${ide}-instructions.md`);
|
||||
|
||||
if (await fs.pathExists(sourceDocPath)) {
|
||||
await fs.copy(sourceDocPath, targetDocPath, { overwrite: true });
|
||||
await this.copyFileWithPlaceholderReplacement(sourceDocPath, targetDocPath, this.bmadFolderName || 'bmad');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -429,9 +429,6 @@ class UI {
|
||||
console.log(chalk.gray('Directory exists and is empty'));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const existingParent = await this.findExistingParent(directory);
|
||||
console.log(chalk.gray(`Will create in: ${existingParent}`));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user