From ce4453c95074a06c8845a476db279bb217eafd5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A5rten=20Angner?= Date: Sun, 1 Mar 2026 10:18:02 +0100 Subject: [PATCH] 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 --- tools/cli/lib/installer.js | 58 ++++++++++++++----------------------- tools/cli/lib/ui.js | 59 ++++++++++++++++++++++++++++++++------ 2 files changed, 73 insertions(+), 44 deletions(-) diff --git a/tools/cli/lib/installer.js b/tools/cli/lib/installer.js index 8be038448..99e7606da 100644 --- a/tools/cli/lib/installer.js +++ b/tools/cli/lib/installer.js @@ -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/) diff --git a/tools/cli/lib/ui.js b/tools/cli/lib/ui.js index c7ca0294f..4fe655565 100644 --- a/tools/cli/lib/ui.js +++ b/tools/cli/lib/ui.js @@ -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, }; }