Installer: ask update/migrate first, skip config questions on update

Restructure the prompt flow so the update/fresh/migrate decision comes
before config questions. On update, config.yaml is preserved so asking
project name, output folder, IDEs etc. is unnecessary. Extract ides and
root_folder from saved config for IDE setup and folder creation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Mårten Angner 2026-03-01 10:18:02 +01:00
parent 8ba623f048
commit ce4453c950
2 changed files with 73 additions and 44 deletions

View File

@ -27,6 +27,7 @@ class Installer {
const { projectDir, wdsFolder, root_folder } = config;
const wdsDir = path.join(projectDir, wdsFolder);
const detection = config._detection || { type: 'fresh' };
const action = config._action || 'fresh';
// Handle legacy _wds/ → _bmad/wds/ migration
if (detection.type === 'legacy' && wdsFolder !== '_wds') {
@ -47,45 +48,30 @@ class Installer {
migrateSpinner.succeed(`Legacy _wds/ removed — installing fresh at ${wdsFolder}/`);
}
// Check if already installed at target path
if (await fs.pathExists(wdsDir)) {
console.log(chalk.yellow(`\n ${wdsFolder}/ already exists.`));
const { action } = await inquirer.prompt([
{
type: 'list',
name: 'action',
message: 'What would you like to do?',
choices: [
{ name: 'Update - Replace WDS files, keep config.yaml', value: 'update' },
{ name: 'Fresh install - Remove everything and start over', value: 'fresh' },
{ name: 'Cancel', value: 'cancel' },
],
},
]);
if (action === 'cancel') {
return { success: false };
// Handle update vs fresh for existing target path
if (action === 'update' && await fs.pathExists(wdsDir)) {
// Preserve config.yaml during update
const configPath = path.join(wdsDir, 'config.yaml');
if (!config._savedConfigYaml && await fs.pathExists(configPath)) {
config._savedConfigYaml = await fs.readFile(configPath, 'utf8');
}
if (action === 'fresh') {
const removeSpinner = ora('Removing existing WDS installation...').start();
await fs.remove(wdsDir);
removeSpinner.succeed('Old installation removed');
} else if (action === 'update') {
// Preserve config.yaml during update
const configPath = path.join(wdsDir, 'config.yaml');
let savedConfig = null;
if (await fs.pathExists(configPath)) {
savedConfig = await fs.readFile(configPath, 'utf8');
}
const removeSpinner = ora('Updating WDS files...').start();
await fs.remove(wdsDir);
removeSpinner.succeed('Old files cleared');
} else if (action === 'fresh' && await fs.pathExists(wdsDir)) {
const removeSpinner = ora('Removing existing WDS installation...').start();
await fs.remove(wdsDir);
removeSpinner.succeed('Old installation removed');
}
const removeSpinner = ora('Updating WDS files...').start();
await fs.remove(wdsDir);
removeSpinner.succeed('Old files cleared');
// Will be restored after copy
config._savedConfigYaml = savedConfig;
}
// On update, extract ides and root_folder from saved config
if (action === 'update' && config._savedConfigYaml) {
try {
const savedData = yaml.load(config._savedConfigYaml);
if (!config.ides && savedData.ides) config.ides = savedData.ides;
if (!config.root_folder && savedData.output_folder) config.root_folder = savedData.output_folder;
} catch { /* ignore parse errors, defaults will apply */ }
}
// Ensure parent directory exists (for _bmad/wds/)

View File

@ -58,33 +58,75 @@ class UI {
console.log(chalk.white(` Target: ${chalk.cyan(projectDir)}`));
// Handle legacy _wds/ detection
let wdsFolder = detection.folder;
let action = 'fresh';
// --- Existing installation: ask update/fresh/migrate FIRST ---
if (detection.type === 'legacy') {
console.log(chalk.yellow(`\n Found legacy installation at ${chalk.white(LEGACY_WDS_FOLDER + '/')}`));
console.log(chalk.dim(` BMAD standard path is ${chalk.white(WDS_FOLDER + '/')}\n`));
const { migrationChoice } = await inquirer.prompt([
const { choice } = await inquirer.prompt([
{
type: 'list',
name: 'migrationChoice',
name: 'choice',
message: 'How would you like to proceed?',
choices: [
{ name: `Migrate to ${WDS_FOLDER}/ (recommended)`, value: 'migrate' },
{ name: `Keep at ${LEGACY_WDS_FOLDER}/ (legacy)`, value: 'keep' },
{ name: `Update & migrate to ${WDS_FOLDER}/ (recommended)`, value: 'migrate-update' },
{ name: `Update at ${LEGACY_WDS_FOLDER}/ (keep legacy path)`, value: 'legacy-update' },
{ name: 'Fresh install (remove everything and start over)', value: 'fresh' },
{ name: 'Cancel', value: 'cancel' },
],
},
]);
if (migrationChoice === 'keep') {
if (choice === 'cancel') return { cancelled: true };
if (choice === 'migrate-update') {
action = 'update';
// wdsFolder stays as WDS_FOLDER (_bmad/wds)
} else if (choice === 'legacy-update') {
action = 'update';
wdsFolder = LEGACY_WDS_FOLDER;
} else {
action = 'fresh';
}
// 'migrate' keeps the default WDS_FOLDER — installer.js handles the actual move
} else if (detection.type === 'bmad') {
console.log(chalk.dim(`\n Found existing installation at ${chalk.white(WDS_FOLDER + '/')}\n`));
const { choice } = await inquirer.prompt([
{
type: 'list',
name: 'choice',
message: 'What would you like to do?',
choices: [
{ name: 'Update - Replace WDS files, keep config.yaml', value: 'update' },
{ name: 'Fresh install - Remove everything and start over', value: 'fresh' },
{ name: 'Cancel', value: 'cancel' },
],
},
]);
if (choice === 'cancel') return { cancelled: true };
action = choice;
} else {
console.log(chalk.dim(` Agents and workflows will be installed in ${chalk.white(wdsFolder + '/')}\n`));
}
// 5-question installer
// --- Update: skip config questions, config.yaml will be preserved ---
if (action === 'update') {
console.log(chalk.dim(' Existing config.yaml will be preserved.\n'));
return {
projectDir,
wdsFolder,
_detection: detection,
_action: action,
cancelled: false,
};
}
// --- Fresh install: ask all config questions ---
const answers = await inquirer.prompt([
{
type: 'input',
@ -154,6 +196,7 @@ class UI {
...answers,
wdsFolder,
_detection: detection,
_action: action,
cancelled: false,
};
}