v4-v6 upgrade improvement and warning about file auto backup

This commit is contained in:
Brian Madison
2025-09-30 19:42:12 -05:00
parent df0c3e4bae
commit e7fcc56cc3
314 changed files with 38192 additions and 32 deletions

View File

@@ -191,14 +191,46 @@ class Detector {
};
const offenders = [];
if (await existsCaseSensitive(projectDir, ['.bmad-core'])) {
offenders.push(path.join(projectDir, '.bmad-core'));
// Find all directories starting with .bmad, bmad, or Bmad
try {
const entries = await fs.readdir(projectDir, { withFileTypes: true });
for (const entry of entries) {
if (entry.isDirectory()) {
const name = entry.name;
// Match .bmad*, bmad* (lowercase), or Bmad* (capital B)
// BUT exclude 'bmad' exactly (that's the new v6 installation directory)
if ((name.startsWith('.bmad') || name.startsWith('bmad') || name.startsWith('Bmad')) && name !== 'bmad') {
offenders.push(path.join(projectDir, entry.name));
}
}
}
} catch {
// Ignore errors reading directory
}
if (await existsCaseSensitive(projectDir, ['.claude', 'commands', 'BMad'])) {
offenders.push(path.join(projectDir, '.claude', 'commands', 'BMad'));
}
if (await existsCaseSensitive(projectDir, ['.crush', 'commands', 'BMad'])) {
offenders.push(path.join(projectDir, '.crush', 'commands', 'BMad'));
// Check inside various IDE command folders for legacy bmad folders
// List of IDE config folders that might have commands directories
const ideConfigFolders = ['.claude', '.crush', '.continue', '.cursor', '.windsurf', '.cline', '.roo-cline'];
for (const ideFolder of ideConfigFolders) {
const commandsPath = path.join(projectDir, ideFolder, 'commands');
if (await fs.pathExists(commandsPath)) {
try {
const commandEntries = await fs.readdir(commandsPath, { withFileTypes: true });
for (const entry of commandEntries) {
if (entry.isDirectory()) {
const name = entry.name;
// Find bmad-related folders (excluding exact 'bmad' if it exists)
if ((name.startsWith('bmad') || name.startsWith('Bmad') || name === 'BMad') && name !== 'bmad') {
offenders.push(path.join(commandsPath, entry.name));
}
}
}
} catch {
// Ignore errors reading commands directory
}
}
}
return { hasLegacyV4: offenders.length > 0, offenders };

View File

@@ -119,12 +119,11 @@ class Installer {
// Display welcome message
CLIUtils.displaySection('BMAD™ Installation', 'Version ' + require(path.join(getProjectRoot(), 'package.json')).version);
// Preflight: Block legacy BMAD v4 footprints before any prompts/writes
// Preflight: Handle legacy BMAD v4 footprints before any prompts/writes
const projectDir = path.resolve(config.directory);
const legacyV4 = await this.detector.detectLegacyV4(projectDir);
if (legacyV4.hasLegacyV4) {
const error = this.createLegacyV4Error(legacyV4);
throw error;
await this.handleLegacyV4Migration(projectDir, legacyV4);
}
// If core config was pre-collected (from interactive mode), use it
@@ -837,36 +836,90 @@ class Installer {
}
/**
* Private: Create formatted error for legacy BMAD v4 detection
* Handle legacy BMAD v4 migration with automatic backup
* @param {string} projectDir - Project directory
* @param {Object} legacyV4 - Legacy V4 detection result with offenders array
* @returns {Error} Formatted error with fullMessage property
*/
createLegacyV4Error(legacyV4) {
const error = new Error('Legacy BMAD v4 artefacts detected in project. Remove them to continue.');
async handleLegacyV4Migration(projectDir, legacyV4) {
console.log(chalk.yellow.bold('\n⚠ Legacy BMAD v4 detected'));
console.log(chalk.dim('The installer found legacy artefacts in your project.\n'));
// Build the complete formatted message using template literals
const headerMessage = `
${chalk.red.bold('Blocked: Legacy BMAD v4 detected')}
The installer found legacy artefacts in your project.`;
// Separate .bmad* folders (auto-backup) from other offending paths (manual cleanup)
const bmadFolders = legacyV4.offenders.filter((p) => {
const name = path.basename(p);
return name.startsWith('.bmad'); // Only dot-prefixed folders get auto-backed up
});
const otherOffenders = legacyV4.offenders.filter((p) => {
const name = path.basename(p);
return !name.startsWith('.bmad'); // Everything else is manual cleanup
});
const offendersMessage = `
Offending paths:
${legacyV4.offenders.map((p) => ` - ${p}`).join('\n')}
const inquirer = require('inquirer');
Cleanup commands you can copy/paste:
${chalk.cyan('macOS/Linux:')}
${legacyV4.offenders.map((p) => ` rm -rf '${p}'`).join('\n')}
${chalk.cyan('Windows:')}
${legacyV4.offenders.map((p) => ` rmdir /S /Q "${p}"`).join('\n')}`;
// Show warning for other offending paths FIRST
if (otherOffenders.length > 0) {
console.log(chalk.yellow('⚠️ Recommended cleanup:'));
console.log(chalk.dim('It is recommended to remove the following items before proceeding:\n'));
for (const p of otherOffenders) console.log(chalk.dim(` - ${p}`));
const footerMessage = `
Remove the listed paths (case sensitive) and rerun install.
Note: You may also want to remove other BMAD-related v4 files/folders left over in this project. If you have customizations, back them up or migrate them before deleting.`;
console.log(chalk.cyan('\nCleanup commands you can copy/paste:'));
console.log(chalk.dim('macOS/Linux:'));
for (const p of otherOffenders) console.log(chalk.dim(` rm -rf '${p}'`));
console.log(chalk.dim('Windows:'));
for (const p of otherOffenders) console.log(chalk.dim(` rmdir /S /Q "${p}"`));
// Attach the complete formatted message
error.fullMessage = headerMessage + offendersMessage + footerMessage;
const { cleanedUp } = await inquirer.prompt([
{
type: 'confirm',
name: 'cleanedUp',
message: 'Have you completed the recommended cleanup? (You can proceed without it, but it is recommended)',
default: false,
},
]);
return error;
if (cleanedUp) {
console.log(chalk.green('✓ Cleanup acknowledged\n'));
} else {
console.log(chalk.yellow('⚠️ Proceeding without recommended cleanup\n'));
}
}
// Handle .bmad* folders with automatic backup
if (bmadFolders.length > 0) {
console.log(chalk.cyan('The following legacy folders will be moved to v4-backup:'));
for (const p of bmadFolders) console.log(chalk.dim(` - ${p}`));
const { proceed } = await inquirer.prompt([
{
type: 'confirm',
name: 'proceed',
message: 'Proceed with backing up legacy v4 folders?',
default: true,
},
]);
if (proceed) {
const backupDir = path.join(projectDir, 'v4-backup');
await fs.ensureDir(backupDir);
for (const folder of bmadFolders) {
const folderName = path.basename(folder);
const backupPath = path.join(backupDir, folderName);
// If backup already exists, add timestamp
let finalBackupPath = backupPath;
if (await fs.pathExists(backupPath)) {
const timestamp = new Date().toISOString().replaceAll(/[:.]/g, '-').split('T')[0];
finalBackupPath = path.join(backupDir, `${folderName}-${timestamp}`);
}
await fs.move(folder, finalBackupPath, { overwrite: false });
console.log(chalk.green(`✓ Moved ${folderName} to ${path.relative(projectDir, finalBackupPath)}`));
}
} else {
throw new Error('Installation cancelled by user');
}
}
}
/**