314 lines
9.3 KiB
JavaScript
Raw Normal View History

2025-06-14 15:06:41 -05:00
const chalk = require("chalk");
const ora = require("ora");
const path = require("path");
const configLoader = require("./config-loader");
const fileManager = require("./file-manager");
const ideSetup = require("./ide-setup");
2025-06-12 22:38:24 -05:00
class Installer {
async install(config) {
2025-06-14 15:06:41 -05:00
const spinner = ora("Installing BMAD Method...").start();
2025-06-12 22:38:24 -05:00
try {
// Resolve installation directory
const installDir = path.resolve(config.directory);
2025-06-14 15:06:41 -05:00
2025-06-12 22:38:24 -05:00
// Check if directory already exists
if (await fileManager.pathExists(installDir)) {
const manifest = await fileManager.readManifest(installDir);
if (manifest) {
2025-06-14 15:06:41 -05:00
spinner.fail("BMAD is already installed in this directory");
console.log(
chalk.yellow(
'\nUse "bmad update" to update the existing installation'
)
);
2025-06-12 22:38:24 -05:00
return;
}
}
2025-06-14 15:06:41 -05:00
2025-06-12 22:38:24 -05:00
let files = [];
2025-06-14 15:06:41 -05:00
if (config.installType === "full") {
// Full installation - copy entire .bmad-core folder as a subdirectory
2025-06-14 15:06:41 -05:00
spinner.text = "Copying complete .bmad-core folder...";
2025-06-12 22:38:24 -05:00
const sourceDir = configLoader.getBmadCorePath();
2025-06-14 15:06:41 -05:00
const bmadCoreDestDir = path.join(installDir, ".bmad-core");
2025-06-12 22:38:24 -05:00
await fileManager.copyDirectory(sourceDir, bmadCoreDestDir);
2025-06-14 15:06:41 -05:00
2025-06-12 22:38:24 -05:00
// Get list of all files for manifest
2025-06-14 15:06:41 -05:00
const glob = require("glob");
files = glob
.sync("**/*", {
cwd: bmadCoreDestDir,
nodir: true,
ignore: ["**/.git/**", "**/node_modules/**"],
})
.map((file) => path.join(".bmad-core", file));
} else if (config.installType === "single-agent") {
2025-06-12 22:38:24 -05:00
// Single agent installation
spinner.text = `Installing ${config.agent} agent...`;
2025-06-14 15:06:41 -05:00
2025-06-12 22:38:24 -05:00
// Copy agent file
const agentPath = configLoader.getAgentPath(config.agent);
2025-06-14 15:06:41 -05:00
const destAgentPath = path.join(
installDir,
"agents",
`${config.agent}.md`
);
2025-06-12 22:38:24 -05:00
await fileManager.copyFile(agentPath, destAgentPath);
files.push(`agents/${config.agent}.md`);
2025-06-14 15:06:41 -05:00
2025-06-12 22:38:24 -05:00
// Copy dependencies
2025-06-14 15:06:41 -05:00
const dependencies = await configLoader.getAgentDependencies(
config.agent
);
2025-06-12 22:38:24 -05:00
const sourceBase = configLoader.getBmadCorePath();
2025-06-14 15:06:41 -05:00
2025-06-12 22:38:24 -05:00
for (const dep of dependencies) {
spinner.text = `Copying dependency: ${dep}`;
2025-06-14 15:06:41 -05:00
if (dep.includes("*")) {
2025-06-12 22:38:24 -05:00
// Handle glob patterns
const copiedFiles = await fileManager.copyGlobPattern(
2025-06-14 15:06:41 -05:00
dep.replace(".bmad-core/", ""),
2025-06-12 22:38:24 -05:00
sourceBase,
installDir
);
files.push(...copiedFiles);
} else {
// Handle single files
2025-06-14 15:06:41 -05:00
const sourcePath = path.join(
sourceBase,
dep.replace(".bmad-core/", "")
);
const destPath = path.join(
installDir,
dep.replace(".bmad-core/", "")
);
2025-06-12 22:38:24 -05:00
if (await fileManager.copyFile(sourcePath, destPath)) {
2025-06-14 15:06:41 -05:00
files.push(dep.replace(".bmad-core/", ""));
2025-06-12 22:38:24 -05:00
}
}
}
}
2025-06-14 15:06:41 -05:00
2025-06-12 22:38:24 -05:00
// Set up IDE integration if requested
if (config.ide) {
spinner.text = `Setting up ${config.ide} integration...`;
// For full installations, IDE rules should be in the root install dir, not .bmad-core
2025-06-12 22:38:24 -05:00
await ideSetup.setup(config.ide, installDir, config.agent);
}
2025-06-14 15:06:41 -05:00
2025-06-12 22:38:24 -05:00
// Create manifest
2025-06-14 15:06:41 -05:00
spinner.text = "Creating installation manifest...";
2025-06-12 22:38:24 -05:00
await fileManager.createManifest(installDir, config, files);
2025-06-14 15:06:41 -05:00
spinner.succeed("Installation complete!");
2025-06-12 22:38:24 -05:00
// Show success message
2025-06-14 15:06:41 -05:00
console.log(chalk.green("\n✓ BMAD Method installed successfully!\n"));
2025-06-12 22:38:24 -05:00
if (config.ide) {
const ideConfig = await configLoader.getIdeConfiguration(config.ide);
if (ideConfig && ideConfig.instructions) {
2025-06-14 15:06:41 -05:00
console.log(
chalk.bold("To use BMAD agents in " + ideConfig.name + ":")
);
2025-06-12 22:38:24 -05:00
console.log(ideConfig.instructions);
}
} else {
2025-06-14 15:06:41 -05:00
console.log(chalk.yellow("No IDE configuration was set up."));
console.log(
"You can manually configure your IDE using the agent files in:",
installDir
);
2025-06-12 22:38:24 -05:00
}
2025-06-14 15:06:41 -05:00
if (config.installType === "single-agent") {
console.log(
chalk.dim(
"\nNeed other agents? Run: npx bmad-method install --agent=<name>"
)
);
console.log(
chalk.dim("Need everything? Run: npx bmad-method install --full")
);
2025-06-12 22:38:24 -05:00
}
} catch (error) {
2025-06-14 15:06:41 -05:00
spinner.fail("Installation failed");
2025-06-12 22:38:24 -05:00
throw error;
}
}
async update(options) {
2025-06-14 15:06:41 -05:00
const spinner = ora("Checking for updates...").start();
2025-06-12 22:38:24 -05:00
try {
// Find existing installation
const installDir = await this.findInstallation();
if (!installDir) {
2025-06-14 15:06:41 -05:00
spinner.fail("No BMAD installation found");
2025-06-12 22:38:24 -05:00
return;
}
2025-06-14 15:06:41 -05:00
2025-06-12 22:38:24 -05:00
const manifest = await fileManager.readManifest(installDir);
if (!manifest) {
2025-06-14 15:06:41 -05:00
spinner.fail("Invalid installation - manifest not found");
2025-06-12 22:38:24 -05:00
return;
}
2025-06-14 15:06:41 -05:00
2025-06-12 22:38:24 -05:00
// Check for modified files
2025-06-14 15:06:41 -05:00
spinner.text = "Checking for modified files...";
const modifiedFiles = await fileManager.checkModifiedFiles(
installDir,
manifest
);
2025-06-12 22:38:24 -05:00
if (modifiedFiles.length > 0 && !options.force) {
2025-06-14 15:06:41 -05:00
spinner.warn("Found modified files");
console.log(chalk.yellow("\nThe following files have been modified:"));
modifiedFiles.forEach((file) => console.log(` - ${file}`));
2025-06-12 22:38:24 -05:00
if (!options.dryRun) {
2025-06-14 15:06:41 -05:00
console.log(
chalk.yellow("\nUse --force to overwrite modified files")
);
console.log(chalk.yellow("or manually backup your changes first"));
2025-06-12 22:38:24 -05:00
}
return;
}
2025-06-14 15:06:41 -05:00
2025-06-12 22:38:24 -05:00
if (options.dryRun) {
2025-06-14 15:06:41 -05:00
spinner.info("Dry run - no changes will be made");
console.log("\nFiles that would be updated:");
manifest.files.forEach((file) => console.log(` - ${file.path}`));
2025-06-12 22:38:24 -05:00
return;
}
2025-06-14 15:06:41 -05:00
2025-06-12 22:38:24 -05:00
// Perform update
2025-06-14 15:06:41 -05:00
spinner.text = "Updating files...";
2025-06-12 22:38:24 -05:00
// Backup modified files if forcing
if (modifiedFiles.length > 0 && options.force) {
for (const file of modifiedFiles) {
const filePath = path.join(installDir, file);
const backupPath = await fileManager.backupFile(filePath);
2025-06-14 15:06:41 -05:00
console.log(
chalk.dim(` Backed up: ${file}${path.basename(backupPath)}`)
);
2025-06-12 22:38:24 -05:00
}
}
2025-06-14 15:06:41 -05:00
2025-06-12 22:38:24 -05:00
// Re-run installation with same config
const config = {
installType: manifest.install_type,
agent: manifest.agent,
directory: installDir,
2025-06-14 15:06:41 -05:00
ide: manifest.ide_setup,
2025-06-12 22:38:24 -05:00
};
2025-06-14 15:06:41 -05:00
2025-06-12 22:38:24 -05:00
await this.install(config);
} catch (error) {
2025-06-14 15:06:41 -05:00
spinner.fail("Update failed");
2025-06-12 22:38:24 -05:00
throw error;
}
}
async listAgents() {
const agents = await configLoader.getAvailableAgents();
2025-06-14 15:06:41 -05:00
console.log(chalk.bold("\nAvailable BMAD Agents:\n"));
agents.forEach((agent) => {
2025-06-12 22:38:24 -05:00
console.log(chalk.cyan(` ${agent.id.padEnd(20)}`), agent.description);
});
2025-06-14 15:06:41 -05:00
console.log(
chalk.dim("\nInstall with: npx bmad-method install --agent=<id>\n")
);
2025-06-12 22:38:24 -05:00
}
async showStatus() {
const installDir = await this.findInstallation();
2025-06-14 15:06:41 -05:00
2025-06-12 22:38:24 -05:00
if (!installDir) {
2025-06-14 15:06:41 -05:00
console.log(
chalk.yellow("No BMAD installation found in current directory tree")
);
2025-06-12 22:38:24 -05:00
return;
}
2025-06-14 15:06:41 -05:00
2025-06-12 22:38:24 -05:00
const manifest = await fileManager.readManifest(installDir);
2025-06-14 15:06:41 -05:00
2025-06-12 22:38:24 -05:00
if (!manifest) {
2025-06-14 15:06:41 -05:00
console.log(chalk.red("Invalid installation - manifest not found"));
2025-06-12 22:38:24 -05:00
return;
}
2025-06-14 15:06:41 -05:00
console.log(chalk.bold("\nBMAD Installation Status:\n"));
2025-06-12 22:38:24 -05:00
console.log(` Directory: ${installDir}`);
console.log(` Version: ${manifest.version}`);
2025-06-14 15:06:41 -05:00
console.log(
` Installed: ${new Date(
manifest.installed_at
).toLocaleDateString()}`
);
2025-06-12 22:38:24 -05:00
console.log(` Type: ${manifest.install_type}`);
2025-06-14 15:06:41 -05:00
2025-06-12 22:38:24 -05:00
if (manifest.agent) {
console.log(` Agent: ${manifest.agent}`);
}
2025-06-14 15:06:41 -05:00
2025-06-12 22:38:24 -05:00
if (manifest.ide_setup) {
console.log(` IDE Setup: ${manifest.ide_setup}`);
}
2025-06-14 15:06:41 -05:00
2025-06-12 22:38:24 -05:00
console.log(` Total Files: ${manifest.files.length}`);
2025-06-14 15:06:41 -05:00
2025-06-12 22:38:24 -05:00
// Check for modifications
2025-06-14 15:06:41 -05:00
const modifiedFiles = await fileManager.checkModifiedFiles(
installDir,
manifest
);
2025-06-12 22:38:24 -05:00
if (modifiedFiles.length > 0) {
console.log(chalk.yellow(` Modified Files: ${modifiedFiles.length}`));
}
2025-06-14 15:06:41 -05:00
console.log("");
2025-06-12 22:38:24 -05:00
}
async getAvailableAgents() {
return configLoader.getAvailableAgents();
}
async findInstallation() {
// Look for .bmad-core in current directory or parent directories
2025-06-12 22:38:24 -05:00
let currentDir = process.cwd();
2025-06-14 15:06:41 -05:00
2025-06-12 22:38:24 -05:00
while (currentDir !== path.dirname(currentDir)) {
2025-06-14 15:06:41 -05:00
const bmadDir = path.join(currentDir, ".bmad-core");
const manifestPath = path.join(bmadDir, "install-manifest.yml");
2025-06-12 22:38:24 -05:00
if (await fileManager.pathExists(manifestPath)) {
return bmadDir;
}
2025-06-14 15:06:41 -05:00
2025-06-12 22:38:24 -05:00
currentDir = path.dirname(currentDir);
}
2025-06-14 15:06:41 -05:00
// Also check if we're inside a .bmad-core directory
2025-06-14 15:06:41 -05:00
if (path.basename(process.cwd()) === ".bmad-core") {
const manifestPath = path.join(process.cwd(), "install-manifest.yml");
2025-06-12 22:38:24 -05:00
if (await fileManager.pathExists(manifestPath)) {
return process.cwd();
}
}
2025-06-14 15:06:41 -05:00
2025-06-12 22:38:24 -05:00
return null;
}
}
2025-06-14 15:06:41 -05:00
module.exports = new Installer();