From c31e334dd80f7ae2e01fa026c45989d374222e5e Mon Sep 17 00:00:00 2001 From: Alex Verkhovsky Date: Sun, 22 Mar 2026 01:24:50 -0600 Subject: [PATCH] refactor(installer): remove IdeConfigManager and dead IDE config flow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Delete IdeConfigManager — IDE configs it persisted were empty markers ({_noConfigNeeded: true}) that no code consumed. ExistingInstall already tracks which IDEs are installed via the manifest. Remove _loadIdeConfigurations, collectToolConfigurations (never called), and the ideConfigurations plumbing from install(). --- .../installers/lib/core/ide-config-manager.js | 157 --------------- tools/cli/installers/lib/core/installer.js | 188 ++---------------- 2 files changed, 12 insertions(+), 333 deletions(-) delete mode 100644 tools/cli/installers/lib/core/ide-config-manager.js diff --git a/tools/cli/installers/lib/core/ide-config-manager.js b/tools/cli/installers/lib/core/ide-config-manager.js deleted file mode 100644 index c00c00d48..000000000 --- a/tools/cli/installers/lib/core/ide-config-manager.js +++ /dev/null @@ -1,157 +0,0 @@ -const path = require('node:path'); -const fs = require('fs-extra'); -const yaml = require('yaml'); -const prompts = require('../../../lib/prompts'); - -/** - * Manages IDE configuration persistence - * Saves and loads IDE-specific configurations to/from bmad/_config/ides/ - */ -class IdeConfigManager { - constructor() {} - - /** - * Get path to IDE config directory - * @param {string} bmadDir - BMAD installation directory - * @returns {string} Path to IDE config directory - */ - getIdeConfigDir(bmadDir) { - return path.join(bmadDir, '_config', 'ides'); - } - - /** - * Get path to specific IDE config file - * @param {string} bmadDir - BMAD installation directory - * @param {string} ideName - IDE name (e.g., 'claude-code') - * @returns {string} Path to IDE config file - */ - getIdeConfigPath(bmadDir, ideName) { - return path.join(this.getIdeConfigDir(bmadDir), `${ideName}.yaml`); - } - - /** - * Save IDE configuration - * @param {string} bmadDir - BMAD installation directory - * @param {string} ideName - IDE name - * @param {Object} configuration - IDE-specific configuration object - */ - async saveIdeConfig(bmadDir, ideName, configuration) { - const configDir = this.getIdeConfigDir(bmadDir); - await fs.ensureDir(configDir); - - const configPath = this.getIdeConfigPath(bmadDir, ideName); - const now = new Date().toISOString(); - - // Check if config already exists to preserve configured_date - let configuredDate = now; - if (await fs.pathExists(configPath)) { - try { - const existing = await this.loadIdeConfig(bmadDir, ideName); - if (existing && existing.configured_date) { - configuredDate = existing.configured_date; - } - } catch { - // Ignore errors reading existing config - } - } - - const configData = { - ide: ideName, - configured_date: configuredDate, - last_updated: now, - configuration: configuration || {}, - }; - - // Clean the config to remove any non-serializable values (like functions) - const cleanConfig = structuredClone(configData); - - const yamlContent = yaml.stringify(cleanConfig, { - indent: 2, - lineWidth: 0, - sortKeys: false, - }); - - // Ensure POSIX-compliant final newline - const content = yamlContent.endsWith('\n') ? yamlContent : yamlContent + '\n'; - await fs.writeFile(configPath, content, 'utf8'); - } - - /** - * Load IDE configuration - * @param {string} bmadDir - BMAD installation directory - * @param {string} ideName - IDE name - * @returns {Object|null} IDE configuration or null if not found - */ - async loadIdeConfig(bmadDir, ideName) { - const configPath = this.getIdeConfigPath(bmadDir, ideName); - - if (!(await fs.pathExists(configPath))) { - return null; - } - - try { - const content = await fs.readFile(configPath, 'utf8'); - const config = yaml.parse(content); - return config; - } catch (error) { - await prompts.log.warn(`Failed to load IDE config for ${ideName}: ${error.message}`); - return null; - } - } - - /** - * Load all IDE configurations - * @param {string} bmadDir - BMAD installation directory - * @returns {Object} Map of IDE name to configuration - */ - async loadAllIdeConfigs(bmadDir) { - const configDir = this.getIdeConfigDir(bmadDir); - const configs = {}; - - if (!(await fs.pathExists(configDir))) { - return configs; - } - - try { - const files = await fs.readdir(configDir); - for (const file of files) { - if (file.endsWith('.yaml')) { - const ideName = file.replace('.yaml', ''); - const config = await this.loadIdeConfig(bmadDir, ideName); - if (config) { - configs[ideName] = config.configuration; - } - } - } - } catch (error) { - await prompts.log.warn(`Failed to load IDE configs: ${error.message}`); - } - - return configs; - } - - /** - * Check if IDE has saved configuration - * @param {string} bmadDir - BMAD installation directory - * @param {string} ideName - IDE name - * @returns {boolean} True if configuration exists - */ - async hasIdeConfig(bmadDir, ideName) { - const configPath = this.getIdeConfigPath(bmadDir, ideName); - return await fs.pathExists(configPath); - } - - /** - * Delete IDE configuration - * @param {string} bmadDir - BMAD installation directory - * @param {string} ideName - IDE name - */ - async deleteIdeConfig(bmadDir, ideName) { - const configPath = this.getIdeConfigPath(bmadDir, ideName); - if (await fs.pathExists(configPath)) { - await fs.remove(configPath); - } - } -} - -module.exports = { IdeConfigManager }; diff --git a/tools/cli/installers/lib/core/installer.js b/tools/cli/installers/lib/core/installer.js index 7fc524322..2848ca011 100644 --- a/tools/cli/installers/lib/core/installer.js +++ b/tools/cli/installers/lib/core/installer.js @@ -8,7 +8,6 @@ const { FileOps } = require('../../../lib/file-ops'); const { Config } = require('./config'); const { getProjectRoot, getSourcePath, getModulePath } = require('../../../lib/project-root'); const { ManifestGenerator } = require('./manifest-generator'); -const { IdeConfigManager } = require('./ide-config-manager'); const { CustomHandler } = require('../custom-handler'); const prompts = require('../../../lib/prompts'); const { BMAD_FOLDER_NAME } = require('../ide/shared/path-utils'); @@ -24,7 +23,6 @@ class Installer { this.customModules = new CustomModules(); this.ideManager = new IdeManager(); this.fileOps = new FileOps(); - this.ideConfigManager = new IdeConfigManager(); this.installedFiles = new Set(); // Track all installed files this.bmadFolderName = BMAD_FOLDER_NAME; } @@ -53,11 +51,10 @@ class Installer { await this._prepareUpdateState(paths, config, customConfig, existingInstall, officialModules); } - const ideConfigurations = await this._loadIdeConfigurations(config, customConfig, paths); await this._validateIdeSelection(config); if (customConfig._isUpdate && customConfig._existingInstall) { - await this._removeDeselectedIdes(customConfig._existingInstall, config, ideConfigurations, paths); + await this._removeDeselectedIdes(customConfig._existingInstall, config, paths); } // Results collector for consolidated summary @@ -69,7 +66,7 @@ class Installer { const { officialModuleIds, allModules } = await this._buildModuleLists(config, customConfig, paths); await this._installAndConfigure(config, customConfig, paths, officialModuleIds, allModules, addResult, officialModules); - await this._setupIdes(config, ideConfigurations, allModules, paths, addResult); + await this._setupIdes(config, allModules, paths, addResult); await this._restoreUserFiles(paths, customConfig); @@ -131,38 +128,6 @@ class Installer { } } - /** - * Load IDE configurations from saved state. No prompts. - */ - async _loadIdeConfigurations(config, customConfig, paths) { - const ideConfigurations = {}; - - if (config.isQuickUpdate()) { - const savedIdeConfigs = customConfig._savedIdeConfigs || {}; - for (const ide of config.ides || []) { - ideConfigurations[ide] = savedIdeConfigs[ide] || { _alreadyConfigured: true }; - } - } else { - // Load saved configs for previously configured IDEs - await this.ideManager.ensureInitialized(); - const bmadDir = paths.bmadDir; - const savedIdeConfigs = await this.ideConfigManager.loadAllIdeConfigs(bmadDir); - - const existingInstall = await ExistingInstall.detect(bmadDir); - const previouslyConfigured = existingInstall.ides; - - for (const ide of config.ides || []) { - if (previouslyConfigured.includes(ide) && savedIdeConfigs[ide]) { - ideConfigurations[ide] = savedIdeConfigs[ide]; - } else { - ideConfigurations[ide] = { _noConfigNeeded: true }; - } - } - } - - return ideConfigurations; - } - /** * Fail fast if all selected IDEs are suspended. */ @@ -190,7 +155,7 @@ class Installer { * Remove IDEs that were previously installed but are no longer selected. * No confirmation — the user's IDE selection is the decision. */ - async _removeDeselectedIdes(existingInstall, config, ideConfigurations, paths) { + async _removeDeselectedIdes(existingInstall, config, paths) { const previouslyInstalled = new Set(existingInstall.ides); const newlySelected = new Set(config.ides || []); const toRemove = [...previouslyInstalled].filter((ide) => !newlySelected.has(ide)); @@ -204,7 +169,6 @@ class Installer { if (handler) { await handler.cleanup(paths.projectRoot); } - await this.ideConfigManager.deleteIdeConfig(paths.bmadDir, ide); } catch (error) { await prompts.log.warn(`Warning: Failed to remove ${ide}: ${error.message}`); } @@ -406,7 +370,7 @@ class Installer { /** * Set up IDE integrations for each selected IDE. */ - async _setupIdes(config, ideConfigurations, allModules, paths, addResult) { + async _setupIdes(config, allModules, paths, addResult) { if (config.skipIde || !config.ides || config.ides.length === 0) return; await this.ideManager.ensureInitialized(); @@ -418,30 +382,15 @@ class Installer { } for (const ide of validIdes) { - const ideHasConfig = Boolean(ideConfigurations[ide]); - const originalLog = console.log; - if (!config.verbose && ideHasConfig) { - console.log = () => {}; - } - try { - const setupResult = await this.ideManager.setup(ide, paths.projectRoot, paths.bmadDir, { - selectedModules: allModules || [], - preCollectedConfig: ideConfigurations[ide] || null, - verbose: config.verbose, - silent: ideHasConfig, - }); + const setupResult = await this.ideManager.setup(ide, paths.projectRoot, paths.bmadDir, { + selectedModules: allModules || [], + verbose: config.verbose, + }); - if (ideConfigurations[ide] && !ideConfigurations[ide]._alreadyConfigured) { - await this.ideConfigManager.saveIdeConfig(paths.bmadDir, ide, ideConfigurations[ide]); - } - - if (setupResult.success) { - addResult(ide, 'ok', setupResult.detail || ''); - } else { - addResult(ide, 'error', setupResult.error || 'failed'); - } - } finally { - console.log = originalLog; + if (setupResult.success) { + addResult(ide, 'ok', setupResult.detail || ''); + } else { + addResult(ide, 'error', setupResult.error || 'failed'); } } } @@ -768,115 +717,6 @@ class Installer { /** * Collect Tool/IDE configurations after module configuration * @param {string} projectDir - Project directory - * @param {Array} selectedModules - Selected modules from configuration - * @param {boolean} isFullReinstall - Whether this is a full reinstall - * @param {Array} previousIdes - Previously configured IDEs (for reinstalls) - * @param {Array} preSelectedIdes - Pre-selected IDEs from early prompt (optional) - * @param {boolean} skipPrompts - Skip prompts and use defaults (for --yes flag) - * @returns {Object} Tool/IDE selection and configurations - */ - async collectToolConfigurations( - projectDir, - selectedModules, - isFullReinstall = false, - previousIdes = [], - preSelectedIdes = null, - skipPrompts = false, - ) { - // Use pre-selected IDEs if provided, otherwise prompt - let toolConfig; - if (preSelectedIdes === null) { - // Fallback: prompt for tool selection (backwards compatibility) - const { UI } = require('../../../lib/ui'); - const ui = new UI(); - toolConfig = await ui.promptToolSelection(projectDir); - } else { - // IDEs were already selected during initial prompts - toolConfig = { - ides: preSelectedIdes, - skipIde: !preSelectedIdes || preSelectedIdes.length === 0, - }; - } - - // Check for already configured IDEs - const bmadDir = path.join(projectDir, BMAD_FOLDER_NAME); - - // During full reinstall, use the saved previous IDEs since bmad dir was deleted - // Otherwise detect from existing installation - let previouslyConfiguredIdes; - if (isFullReinstall) { - previouslyConfiguredIdes = []; - } else { - const existingInstall = await ExistingInstall.detect(bmadDir); - previouslyConfiguredIdes = existingInstall.ides; - } - - // Load saved IDE configurations for already-configured IDEs - const savedIdeConfigs = await this.ideConfigManager.loadAllIdeConfigs(bmadDir); - - // Collect IDE-specific configurations if any were selected - const ideConfigurations = {}; - - // First, add saved configs for already-configured IDEs - for (const ide of toolConfig.ides || []) { - if (previouslyConfiguredIdes.includes(ide) && savedIdeConfigs[ide]) { - ideConfigurations[ide] = savedIdeConfigs[ide]; - } - } - - if (!toolConfig.skipIde && toolConfig.ides && toolConfig.ides.length > 0) { - // Ensure IDE manager is initialized - await this.ideManager.ensureInitialized(); - - // Determine which IDEs are newly selected (not previously configured) - const newlySelectedIdes = toolConfig.ides.filter((ide) => !previouslyConfiguredIdes.includes(ide)); - - if (newlySelectedIdes.length > 0) { - // Collect configuration for IDEs that support it - for (const ide of newlySelectedIdes) { - try { - const handler = this.ideManager.handlers.get(ide); - - if (!handler) { - await prompts.log.warn(`Warning: IDE '${ide}' handler not found`); - continue; - } - - // Check if this IDE handler has a collectConfiguration method - // (custom installers like Codex, Kilo may have this) - if (typeof handler.collectConfiguration === 'function') { - await prompts.log.info(`Configuring ${ide}...`); - ideConfigurations[ide] = await handler.collectConfiguration({ - selectedModules: selectedModules || [], - projectDir, - bmadDir, - skipPrompts, - }); - } else { - // Config-driven IDEs don't need configuration - mark as ready - ideConfigurations[ide] = { _noConfigNeeded: true }; - } - } catch (error) { - // IDE doesn't support configuration or has an error - await prompts.log.warn(`Warning: Could not load configuration for ${ide}: ${error.message}`); - } - } - } - - // Log which IDEs are already configured and being kept - const keptIdes = toolConfig.ides.filter((ide) => previouslyConfiguredIdes.includes(ide)); - if (keptIdes.length > 0) { - await prompts.log.message(`Keeping existing configuration for: ${keptIdes.join(', ')}`); - } - } - - return { - ides: toolConfig.ides, - skipIde: toolConfig.skipIde, - configurations: ideConfigurations, - }; - } - /** * Private: Prompt for update action */ @@ -1490,9 +1330,6 @@ class Installer { } } - // Load saved IDE configurations - const savedIdeConfigs = await this.ideConfigManager.loadAllIdeConfigs(bmadDir); - // Get available modules (what we have source for) const availableModulesData = await new OfficialModules().listAvailable(); const availableModules = [...availableModulesData.modules, ...availableModulesData.customModules]; @@ -1614,7 +1451,6 @@ class Installer { actionType: 'install', // Use regular install flow _quickUpdate: true, // Flag to skip certain prompts _preserveModules: skippedModules, // Preserve these in manifest even though we didn't update them - _savedIdeConfigs: savedIdeConfigs, // Pass saved IDE configs to installer _customModuleSources: customModuleSources, // Pass custom module sources for updates _existingModules: installedModules, // Pass all installed modules for manifest generation customContent: config.customContent, // Pass through for re-caching from source