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 { projectDir, wdsFolder, root_folder } = config;
const wdsDir = path.join(projectDir, wdsFolder); const wdsDir = path.join(projectDir, wdsFolder);
const detection = config._detection || { type: 'fresh' }; const detection = config._detection || { type: 'fresh' };
const action = config._action || 'fresh';
// Handle legacy _wds/ → _bmad/wds/ migration // Handle legacy _wds/ → _bmad/wds/ migration
if (detection.type === 'legacy' && wdsFolder !== '_wds') { if (detection.type === 'legacy' && wdsFolder !== '_wds') {
@ -47,45 +48,30 @@ class Installer {
migrateSpinner.succeed(`Legacy _wds/ removed — installing fresh at ${wdsFolder}/`); migrateSpinner.succeed(`Legacy _wds/ removed — installing fresh at ${wdsFolder}/`);
} }
// Check if already installed at target path // Handle update vs fresh for existing target path
if (await fs.pathExists(wdsDir)) { if (action === 'update' && await fs.pathExists(wdsDir)) {
console.log(chalk.yellow(`\n ${wdsFolder}/ already exists.`)); // Preserve config.yaml during update
const { action } = await inquirer.prompt([ const configPath = path.join(wdsDir, 'config.yaml');
{ if (!config._savedConfigYaml && await fs.pathExists(configPath)) {
type: 'list', config._savedConfigYaml = await fs.readFile(configPath, 'utf8');
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 };
} }
if (action === 'fresh') { const removeSpinner = ora('Updating WDS files...').start();
const removeSpinner = ora('Removing existing WDS installation...').start(); await fs.remove(wdsDir);
await fs.remove(wdsDir); removeSpinner.succeed('Old files cleared');
removeSpinner.succeed('Old installation removed'); } else if (action === 'fresh' && await fs.pathExists(wdsDir)) {
} else if (action === 'update') { const removeSpinner = ora('Removing existing WDS installation...').start();
// Preserve config.yaml during update await fs.remove(wdsDir);
const configPath = path.join(wdsDir, 'config.yaml'); removeSpinner.succeed('Old installation removed');
let savedConfig = null; }
if (await fs.pathExists(configPath)) {
savedConfig = await fs.readFile(configPath, 'utf8');
}
const removeSpinner = ora('Updating WDS files...').start(); // On update, extract ides and root_folder from saved config
await fs.remove(wdsDir); if (action === 'update' && config._savedConfigYaml) {
removeSpinner.succeed('Old files cleared'); try {
const savedData = yaml.load(config._savedConfigYaml);
// Will be restored after copy if (!config.ides && savedData.ides) config.ides = savedData.ides;
config._savedConfigYaml = savedConfig; 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/) // Ensure parent directory exists (for _bmad/wds/)

View File

@ -58,33 +58,75 @@ class UI {
console.log(chalk.white(` Target: ${chalk.cyan(projectDir)}`)); console.log(chalk.white(` Target: ${chalk.cyan(projectDir)}`));
// Handle legacy _wds/ detection
let wdsFolder = detection.folder; let wdsFolder = detection.folder;
let action = 'fresh';
// --- Existing installation: ask update/fresh/migrate FIRST ---
if (detection.type === 'legacy') { if (detection.type === 'legacy') {
console.log(chalk.yellow(`\n Found legacy installation at ${chalk.white(LEGACY_WDS_FOLDER + '/')}`)); 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`)); 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', type: 'list',
name: 'migrationChoice', name: 'choice',
message: 'How would you like to proceed?', message: 'How would you like to proceed?',
choices: [ choices: [
{ name: `Migrate to ${WDS_FOLDER}/ (recommended)`, value: 'migrate' }, { name: `Update & migrate to ${WDS_FOLDER}/ (recommended)`, value: 'migrate-update' },
{ name: `Keep at ${LEGACY_WDS_FOLDER}/ (legacy)`, value: 'keep' }, { 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; 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 { } else {
console.log(chalk.dim(` Agents and workflows will be installed in ${chalk.white(wdsFolder + '/')}\n`)); 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([ const answers = await inquirer.prompt([
{ {
type: 'input', type: 'input',
@ -154,6 +196,7 @@ class UI {
...answers, ...answers,
wdsFolder, wdsFolder,
_detection: detection, _detection: detection,
_action: action,
cancelled: false, cancelled: false,
}; };
} }