feat: enhance installer with multi-IDE support and sync version bumping
This commit is contained in:
@@ -1,8 +1,17 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const { program } = require('commander');
|
||||
const inquirer = require('inquirer');
|
||||
const chalk = require('chalk');
|
||||
|
||||
// Dynamic imports for ES modules
|
||||
let chalk, inquirer;
|
||||
|
||||
// Initialize ES modules
|
||||
async function initializeModules() {
|
||||
if (!chalk) {
|
||||
chalk = (await import('chalk')).default;
|
||||
inquirer = (await import('inquirer')).default;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle both execution contexts (from root via npx or from installer directory)
|
||||
let version;
|
||||
@@ -13,13 +22,13 @@ try {
|
||||
installer = require('../lib/installer');
|
||||
} catch (e) {
|
||||
// Fall back to root context (when run via npx from GitHub)
|
||||
console.log(chalk.yellow(`Installer context not found (${e.message}), trying root context...`));
|
||||
console.log(`Installer context not found (${e.message}), trying root context...`);
|
||||
try {
|
||||
version = require('../../../package.json').version;
|
||||
installer = require('../../../tools/installer/lib/installer');
|
||||
} catch (e2) {
|
||||
console.error(chalk.red('Error: Could not load required modules. Please ensure you are running from the correct directory.'));
|
||||
console.error(chalk.yellow('Debug info:'), {
|
||||
console.error('Error: Could not load required modules. Please ensure you are running from the correct directory.');
|
||||
console.error('Debug info:', {
|
||||
__dirname,
|
||||
cwd: process.cwd(),
|
||||
error: e2.message
|
||||
@@ -38,9 +47,10 @@ program
|
||||
.option('-f, --full', 'Install complete .bmad-core folder')
|
||||
.option('-a, --agent <agent>', 'Install specific agent with dependencies')
|
||||
.option('-d, --directory <path>', 'Installation directory (default: .bmad-core)')
|
||||
.option('-i, --ide <ide>', 'Configure for specific IDE (cursor, claude-code, windsurf, roo)')
|
||||
.option('-i, --ide <ide...>', 'Configure for specific IDE(s) - can specify multiple (cursor, claude-code, windsurf, roo)')
|
||||
.action(async (options) => {
|
||||
try {
|
||||
await initializeModules();
|
||||
if (!options.full && !options.agent) {
|
||||
// Interactive mode
|
||||
const answers = await promptInstallation();
|
||||
@@ -51,11 +61,12 @@ program
|
||||
installType: options.full ? 'full' : 'single-agent',
|
||||
agent: options.agent,
|
||||
directory: options.directory || '.bmad-core',
|
||||
ide: options.ide
|
||||
ides: options.ide || []
|
||||
};
|
||||
await installer.install(config);
|
||||
}
|
||||
} catch (error) {
|
||||
if (!chalk) await initializeModules();
|
||||
console.error(chalk.red('Installation failed:'), error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -70,6 +81,7 @@ program
|
||||
try {
|
||||
await installer.update();
|
||||
} catch (error) {
|
||||
if (!chalk) await initializeModules();
|
||||
console.error(chalk.red('Update failed:'), error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -82,6 +94,7 @@ program
|
||||
try {
|
||||
await installer.listAgents();
|
||||
} catch (error) {
|
||||
if (!chalk) await initializeModules();
|
||||
console.error(chalk.red('Error:'), error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -94,12 +107,14 @@ program
|
||||
try {
|
||||
await installer.showStatus();
|
||||
} catch (error) {
|
||||
if (!chalk) await initializeModules();
|
||||
console.error(chalk.red('Error:'), error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
async function promptInstallation() {
|
||||
await initializeModules();
|
||||
console.log(chalk.bold.blue(`\nWelcome to BMAD Method Installer v${version}\n`));
|
||||
|
||||
const answers = {};
|
||||
@@ -153,21 +168,26 @@ async function promptInstallation() {
|
||||
}
|
||||
|
||||
// Ask for IDE configuration
|
||||
const { ide } = await inquirer.prompt([
|
||||
const { ides } = await inquirer.prompt([
|
||||
{
|
||||
type: 'list',
|
||||
name: 'ide',
|
||||
message: 'Which IDE are you using?',
|
||||
type: 'checkbox',
|
||||
name: 'ides',
|
||||
message: 'Which IDE(s) are you using? (Select all that apply)',
|
||||
choices: [
|
||||
{ name: 'Cursor', value: 'cursor' },
|
||||
{ name: 'Claude Code', value: 'claude-code' },
|
||||
{ name: 'Windsurf', value: 'windsurf' },
|
||||
{ name: 'Roo Code', value: 'roo' },
|
||||
{ name: 'Other/Manual setup', value: null }
|
||||
]
|
||||
{ name: 'Roo Code', value: 'roo' }
|
||||
],
|
||||
validate: (answer) => {
|
||||
if (answer.length < 1) {
|
||||
return 'You must choose at least one IDE, or press Ctrl+C to skip IDE setup.';
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
]);
|
||||
answers.ide = ide;
|
||||
answers.ides = ides;
|
||||
|
||||
return answers;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,16 @@ const fs = require("fs-extra");
|
||||
const path = require("path");
|
||||
const crypto = require("crypto");
|
||||
const glob = require("glob");
|
||||
const chalk = require("chalk");
|
||||
|
||||
// Dynamic import for ES module
|
||||
let chalk;
|
||||
|
||||
// Initialize ES modules
|
||||
async function initializeModules() {
|
||||
if (!chalk) {
|
||||
chalk = (await import("chalk")).default;
|
||||
}
|
||||
}
|
||||
|
||||
class FileManager {
|
||||
constructor() {
|
||||
@@ -16,6 +25,7 @@ class FileManager {
|
||||
await fs.copy(source, destination);
|
||||
return true;
|
||||
} catch (error) {
|
||||
await initializeModules();
|
||||
console.error(chalk.red(`Failed to copy ${source}:`), error.message);
|
||||
return false;
|
||||
}
|
||||
@@ -27,6 +37,7 @@ class FileManager {
|
||||
await fs.copy(source, destination);
|
||||
return true;
|
||||
} catch (error) {
|
||||
await initializeModules();
|
||||
console.error(
|
||||
chalk.red(`Failed to copy directory ${source}:`),
|
||||
error.message
|
||||
@@ -77,6 +88,7 @@ class FileManager {
|
||||
install_type: config.installType,
|
||||
agent: config.agent || null,
|
||||
ide_setup: config.ide || null,
|
||||
ides_setup: config.ides || [],
|
||||
files: [],
|
||||
};
|
||||
|
||||
@@ -145,7 +157,12 @@ class FileManager {
|
||||
}
|
||||
|
||||
async ensureDirectory(dirPath) {
|
||||
await fs.ensureDir(dirPath);
|
||||
try {
|
||||
await fs.ensureDir(dirPath);
|
||||
return true;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async pathExists(filePath) {
|
||||
|
||||
@@ -1,10 +1,20 @@
|
||||
const path = require("path");
|
||||
const fileManager = require("./file-manager");
|
||||
const configLoader = require("./config-loader");
|
||||
const chalk = require("chalk");
|
||||
|
||||
// Dynamic import for ES module
|
||||
let chalk;
|
||||
|
||||
// Initialize ES modules
|
||||
async function initializeModules() {
|
||||
if (!chalk) {
|
||||
chalk = (await import("chalk")).default;
|
||||
}
|
||||
}
|
||||
|
||||
class IdeSetup {
|
||||
async setup(ide, installDir, selectedAgent = null) {
|
||||
await initializeModules();
|
||||
const ideConfig = await configLoader.getIdeConfiguration(ide);
|
||||
|
||||
if (!ideConfig) {
|
||||
|
||||
@@ -1,13 +1,25 @@
|
||||
const path = require("node:path");
|
||||
const chalk = require("chalk");
|
||||
const ora = require("ora");
|
||||
const inquirer = require("inquirer");
|
||||
const fileManager = require("./file-manager");
|
||||
const configLoader = require("./config-loader");
|
||||
const ideSetup = require("./ide-setup");
|
||||
|
||||
// Dynamic imports for ES modules
|
||||
let chalk, ora, inquirer;
|
||||
|
||||
// Initialize ES modules
|
||||
async function initializeModules() {
|
||||
if (!chalk) {
|
||||
chalk = (await import("chalk")).default;
|
||||
ora = (await import("ora")).default;
|
||||
inquirer = (await import("inquirer")).default;
|
||||
}
|
||||
}
|
||||
|
||||
class Installer {
|
||||
async install(config) {
|
||||
// Initialize ES modules
|
||||
await initializeModules();
|
||||
|
||||
const spinner = ora("Analyzing installation directory...").start();
|
||||
|
||||
try {
|
||||
@@ -18,6 +30,66 @@ class Installer {
|
||||
installDir = path.dirname(installDir);
|
||||
}
|
||||
|
||||
// Check if directory exists and handle non-existent directories
|
||||
if (!(await fileManager.pathExists(installDir))) {
|
||||
spinner.stop();
|
||||
console.log(chalk.yellow(`\nThe directory ${chalk.bold(installDir)} does not exist.`));
|
||||
|
||||
const { action } = await inquirer.prompt([
|
||||
{
|
||||
type: 'list',
|
||||
name: 'action',
|
||||
message: 'What would you like to do?',
|
||||
choices: [
|
||||
{
|
||||
name: 'Create the directory and continue',
|
||||
value: 'create'
|
||||
},
|
||||
{
|
||||
name: 'Choose a different directory',
|
||||
value: 'change'
|
||||
},
|
||||
{
|
||||
name: 'Cancel installation',
|
||||
value: 'cancel'
|
||||
}
|
||||
]
|
||||
}
|
||||
]);
|
||||
|
||||
if (action === 'cancel') {
|
||||
console.log(chalk.red('Installation cancelled.'));
|
||||
process.exit(0);
|
||||
} else if (action === 'change') {
|
||||
const { newDirectory } = await inquirer.prompt([
|
||||
{
|
||||
type: 'input',
|
||||
name: 'newDirectory',
|
||||
message: 'Enter the new directory path:',
|
||||
validate: (input) => {
|
||||
if (!input.trim()) {
|
||||
return 'Please enter a valid directory path';
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
]);
|
||||
config.directory = newDirectory;
|
||||
return await this.install(config); // Recursive call with new directory
|
||||
} else if (action === 'create') {
|
||||
try {
|
||||
await fileManager.ensureDirectory(installDir);
|
||||
console.log(chalk.green(`✓ Created directory: ${installDir}`));
|
||||
} catch (error) {
|
||||
console.error(chalk.red(`Failed to create directory: ${error.message}`));
|
||||
console.error(chalk.yellow('You may need to check permissions or use a different path.'));
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
spinner.start("Analyzing installation directory...");
|
||||
}
|
||||
|
||||
// Detect current state
|
||||
const state = await this.detectInstallationState(installDir);
|
||||
|
||||
@@ -57,6 +129,8 @@ class Installer {
|
||||
}
|
||||
|
||||
async detectInstallationState(installDir) {
|
||||
// Ensure modules are initialized
|
||||
await initializeModules();
|
||||
const state = {
|
||||
type: "clean",
|
||||
hasV4Manifest: false,
|
||||
@@ -116,6 +190,8 @@ class Installer {
|
||||
}
|
||||
|
||||
async performFreshInstall(config, installDir, spinner) {
|
||||
// Ensure modules are initialized
|
||||
await initializeModules();
|
||||
spinner.text = "Installing BMAD Method...";
|
||||
|
||||
let files = [];
|
||||
@@ -186,9 +262,12 @@ class Installer {
|
||||
}
|
||||
|
||||
// Set up IDE integration if requested
|
||||
if (config.ide) {
|
||||
spinner.text = `Setting up ${config.ide} integration...`;
|
||||
await ideSetup.setup(config.ide, installDir, config.agent);
|
||||
const ides = config.ides || (config.ide ? [config.ide] : []);
|
||||
if (ides.length > 0) {
|
||||
for (const ide of ides) {
|
||||
spinner.text = `Setting up ${ide} integration...`;
|
||||
await ideSetup.setup(ide, installDir, config.agent);
|
||||
}
|
||||
}
|
||||
|
||||
// Create manifest
|
||||
@@ -200,6 +279,8 @@ class Installer {
|
||||
}
|
||||
|
||||
async handleExistingV4Installation(config, installDir, state, spinner) {
|
||||
// Ensure modules are initialized
|
||||
await initializeModules();
|
||||
spinner.stop();
|
||||
|
||||
console.log(chalk.yellow("\n🔍 Found existing BMAD v4 installation"));
|
||||
@@ -236,6 +317,8 @@ class Installer {
|
||||
}
|
||||
|
||||
async handleV3Installation(config, installDir, state, spinner) {
|
||||
// Ensure modules are initialized
|
||||
await initializeModules();
|
||||
spinner.stop();
|
||||
|
||||
console.log(
|
||||
@@ -272,6 +355,8 @@ class Installer {
|
||||
}
|
||||
|
||||
async handleUnknownInstallation(config, installDir, state, spinner) {
|
||||
// Ensure modules are initialized
|
||||
await initializeModules();
|
||||
spinner.stop();
|
||||
|
||||
console.log(chalk.yellow("\n⚠️ Directory contains existing files"));
|
||||
@@ -397,13 +482,16 @@ class Installer {
|
||||
showSuccessMessage(config, installDir) {
|
||||
console.log(chalk.green("\n✓ BMAD Method installed successfully!\n"));
|
||||
|
||||
if (config.ide) {
|
||||
const ideConfig = configLoader.getIdeConfiguration(config.ide);
|
||||
if (ideConfig?.instructions) {
|
||||
console.log(
|
||||
chalk.bold(`To use BMAD agents in ${ideConfig.name}:`)
|
||||
);
|
||||
console.log(ideConfig.instructions);
|
||||
const ides = config.ides || (config.ide ? [config.ide] : []);
|
||||
if (ides.length > 0) {
|
||||
for (const ide of ides) {
|
||||
const ideConfig = configLoader.getIdeConfiguration(ide);
|
||||
if (ideConfig?.instructions) {
|
||||
console.log(
|
||||
chalk.bold(`To use BMAD agents in ${ideConfig.name}:`)
|
||||
);
|
||||
console.log(ideConfig.instructions);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.log(chalk.yellow("No IDE configuration was set up."));
|
||||
@@ -413,6 +501,25 @@ class Installer {
|
||||
);
|
||||
}
|
||||
|
||||
// Information about installation components
|
||||
console.log(chalk.bold("\n🎯 Installation Summary:"));
|
||||
console.log(chalk.green("✓ .bmad-core framework installed with all agents and workflows"));
|
||||
|
||||
if (ides.length > 0) {
|
||||
const ideNames = ides.map(ide => {
|
||||
const ideConfig = configLoader.getIdeConfiguration(ide);
|
||||
return ideConfig?.name || ide;
|
||||
}).join(", ");
|
||||
console.log(chalk.green(`✓ IDE rules and configurations set up for: ${ideNames}`));
|
||||
}
|
||||
|
||||
// Information about web bundles
|
||||
console.log(chalk.bold("\n📦 Web Bundles Available:"));
|
||||
console.log("Self-contained web bundles have been included in your installation:");
|
||||
console.log(chalk.cyan(` ${installDir}/.bmad-core/web-bundles/`));
|
||||
console.log("These bundles work independently without this installation and can be");
|
||||
console.log("shared, moved, or used in other projects as standalone files.");
|
||||
|
||||
if (config.installType === "single-agent") {
|
||||
console.log(
|
||||
chalk.dim(
|
||||
@@ -427,6 +534,8 @@ class Installer {
|
||||
|
||||
// Legacy method for backward compatibility
|
||||
async update() {
|
||||
// Initialize ES modules
|
||||
await initializeModules();
|
||||
console.log(chalk.yellow('The "update" command is deprecated.'));
|
||||
console.log(
|
||||
'Please use "install" instead - it will detect and offer to update existing installations.'
|
||||
@@ -445,6 +554,8 @@ class Installer {
|
||||
}
|
||||
|
||||
async listAgents() {
|
||||
// Initialize ES modules
|
||||
await initializeModules();
|
||||
const agents = await configLoader.getAvailableAgents();
|
||||
|
||||
console.log(chalk.bold("\nAvailable BMAD Agents:\n"));
|
||||
@@ -459,6 +570,8 @@ class Installer {
|
||||
}
|
||||
|
||||
async showStatus() {
|
||||
// Initialize ES modules
|
||||
await initializeModules();
|
||||
const installDir = await this.findInstallation();
|
||||
|
||||
if (!installDir) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "bmad-method",
|
||||
"version": "4.0.1",
|
||||
"version": "4.2.0",
|
||||
"description": "BMAD Method installer - AI-powered Agile development framework",
|
||||
"main": "lib/installer.js",
|
||||
"bin": {
|
||||
|
||||
Reference in New Issue
Block a user