865 lines
27 KiB
JavaScript
Raw Normal View History

const path = require("node:path");
2025-06-14 15:06:41 -05:00
const fileManager = require("./file-manager");
const configLoader = require("./config-loader");
2025-06-14 15:06:41 -05:00
const ideSetup = require("./ide-setup");
2025-06-12 22:38:24 -05:00
// 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;
}
}
2025-06-12 22:38:24 -05:00
class Installer {
async install(config) {
// Initialize ES modules
await initializeModules();
const spinner = ora("Analyzing installation directory...").start();
2025-06-14 15:06:41 -05:00
2025-06-12 22:38:24 -05:00
try {
// Resolve installation directory
let installDir = path.resolve(config.directory);
if (path.basename(installDir) === '.bmad-core') {
// If user points directly to .bmad-core, treat its parent as the project root
installDir = path.dirname(installDir);
}
2025-06-14 15:06:41 -05:00
// 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);
// Handle different states
switch (state.type) {
case "clean":
return await this.performFreshInstall(config, installDir, spinner);
case "v4_existing":
return await this.handleExistingV4Installation(
config,
installDir,
state,
spinner
);
case "v3_existing":
return await this.handleV3Installation(
config,
installDir,
state,
spinner
);
case "unknown_existing":
return await this.handleUnknownInstallation(
config,
installDir,
state,
spinner
2025-06-14 15:06:41 -05:00
);
2025-06-12 22:38:24 -05:00
}
} catch (error) {
spinner.fail("Installation failed");
throw error;
}
}
2025-06-14 15:06:41 -05:00
async detectInstallationState(installDir) {
// Ensure modules are initialized
await initializeModules();
const state = {
type: "clean",
hasV4Manifest: false,
hasV3Structure: false,
hasBmadCore: false,
hasOtherFiles: false,
manifest: null,
};
// Check if directory exists
if (!(await fileManager.pathExists(installDir))) {
return state; // clean install
}
2025-06-14 15:06:41 -05:00
// Check for V4 installation (has .bmad-core with manifest)
const bmadCorePath = path.join(installDir, ".bmad-core");
const manifestPath = path.join(bmadCorePath, "install-manifest.yml");
2025-06-14 15:06:41 -05:00
if (await fileManager.pathExists(manifestPath)) {
state.type = "v4_existing";
state.hasV4Manifest = true;
state.hasBmadCore = true;
2025-06-14 23:49:10 -05:00
state.manifest = await fileManager.readManifest(installDir);
return state;
}
2025-06-14 15:06:41 -05:00
// Check for V3 installation (has bmad-agent directory)
const bmadAgentPath = path.join(installDir, "bmad-agent");
if (await fileManager.pathExists(bmadAgentPath)) {
state.type = "v3_existing";
state.hasV3Structure = true;
return state;
}
2025-06-14 15:06:41 -05:00
// Check for .bmad-core without manifest (broken V4 or manual copy)
if (await fileManager.pathExists(bmadCorePath)) {
state.type = "unknown_existing";
state.hasBmadCore = true;
return state;
}
2025-06-14 15:06:41 -05:00
// Check if directory has other files
const glob = require("glob");
const files = glob.sync("**/*", {
cwd: installDir,
nodir: true,
ignore: ["**/.git/**", "**/node_modules/**"],
});
2025-06-14 15:06:41 -05:00
if (files.length > 0) {
// Directory has other files, but no BMAD installation.
// Treat as clean install but record that it isn't empty.
state.hasOtherFiles = true;
}
return state; // clean install
}
async performFreshInstall(config, installDir, spinner) {
// Ensure modules are initialized
await initializeModules();
spinner.text = "Installing BMAD Method...";
let files = [];
if (config.installType === "full") {
// Full installation - copy entire .bmad-core folder as a subdirectory
spinner.text = "Copying complete .bmad-core folder...";
const sourceDir = configLoader.getBmadCorePath();
const bmadCoreDestDir = path.join(installDir, ".bmad-core");
await fileManager.copyDirectory(sourceDir, bmadCoreDestDir);
// Get list of all files for manifest
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") {
// Single agent installation
spinner.text = `Installing ${config.agent} agent...`;
// Copy agent file
const agentPath = configLoader.getAgentPath(config.agent);
const destAgentPath = path.join(
installDir,
".bmad-core",
"agents",
`${config.agent}.md`
);
await fileManager.copyFile(agentPath, destAgentPath);
files.push(`.bmad-core/agents/${config.agent}.md`);
2025-06-14 15:06:41 -05:00
// Copy dependencies
const dependencies = await configLoader.getAgentDependencies(
config.agent
);
const sourceBase = configLoader.getBmadCorePath();
2025-06-14 15:06:41 -05:00
for (const dep of dependencies) {
spinner.text = `Copying dependency: ${dep}`;
2025-06-14 15:06:41 -05:00
if (dep.includes("*")) {
// Handle glob patterns
const copiedFiles = await fileManager.copyGlobPattern(
dep.replace(".bmad-core/", ""),
sourceBase,
path.join(installDir, ".bmad-core")
);
files.push(...copiedFiles.map(f => `.bmad-core/${f}`));
} else {
// Handle single files
const sourcePath = path.join(
sourceBase,
dep.replace(".bmad-core/", "")
2025-06-14 15:06:41 -05:00
);
const destPath = path.join(
installDir,
dep
);
if (await fileManager.copyFile(sourcePath, destPath)) {
files.push(dep);
}
2025-06-12 22:38:24 -05:00
}
}
} else if (config.installType === "team") {
// Team installation
spinner.text = `Installing ${config.team} team...`;
// Get team dependencies
const teamDependencies = await configLoader.getTeamDependencies(config.team);
const sourceBase = configLoader.getBmadCorePath();
// Install all team dependencies
for (const dep of teamDependencies) {
spinner.text = `Copying team dependency: ${dep}`;
if (dep.includes("*")) {
// Handle glob patterns
const copiedFiles = await fileManager.copyGlobPattern(
dep.replace(".bmad-core/", ""),
sourceBase,
path.join(installDir, ".bmad-core")
);
files.push(...copiedFiles.map(f => `.bmad-core/${f}`));
} else {
// Handle single files
const sourcePath = path.join(sourceBase, dep.replace(".bmad-core/", ""));
const destPath = path.join(installDir, dep);
if (await fileManager.copyFile(sourcePath, destPath)) {
files.push(dep);
}
}
}
} else if (config.installType === "expansion-only") {
// Expansion-only installation - create minimal .bmad-core structure
spinner.text = "Creating minimal .bmad-core structure for expansion packs...";
const bmadCoreDestDir = path.join(installDir, ".bmad-core");
await fileManager.ensureDirectory(bmadCoreDestDir);
// Create basic directory structure
const dirs = ['agents', 'agent-teams', 'templates', 'tasks', 'checklists', 'workflows', 'data', 'utils', 'schemas'];
for (const dir of dirs) {
await fileManager.ensureDirectory(path.join(bmadCoreDestDir, dir));
}
// Copy minimal required files (schemas, utils, etc.)
const sourceBase = configLoader.getBmadCorePath();
const essentialFiles = [
'schemas/**/*',
'utils/**/*'
];
for (const pattern of essentialFiles) {
const copiedFiles = await fileManager.copyGlobPattern(
pattern,
sourceBase,
bmadCoreDestDir
);
files.push(...copiedFiles.map(f => `.bmad-core/${f}`));
}
}
2025-06-14 15:06:41 -05:00
// Install expansion packs if requested
const expansionFiles = await this.installExpansionPacks(installDir, config.expansionPacks, spinner);
files.push(...expansionFiles);
// Install web bundles if requested
if (config.includeWebBundles && config.webBundlesDirectory) {
spinner.text = "Installing web bundles...";
await this.installWebBundles(config.webBundlesDirectory, spinner);
}
// Set up IDE integration if requested
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);
}
2025-06-12 22:38:24 -05:00
}
// Create manifest
spinner.text = "Creating installation manifest...";
await fileManager.createManifest(installDir, config, files);
spinner.succeed("Installation complete!");
this.showSuccessMessage(config, installDir);
2025-06-12 22:38:24 -05:00
}
async handleExistingV4Installation(config, installDir, state, spinner) {
// Ensure modules are initialized
await initializeModules();
spinner.stop();
2025-06-14 15:06:41 -05:00
console.log(chalk.yellow("\n🔍 Found existing BMAD v4 installation"));
console.log(` Directory: ${installDir}`);
console.log(` Version: ${state.manifest.version}`);
console.log(
` Installed: ${new Date(
state.manifest.installed_at
).toLocaleDateString()}`
);
const { action } = await inquirer.prompt([
{
type: "list",
name: "action",
message: "What would you like to do?",
choices: [
{ name: "Update existing installation", value: "update" },
{ name: "Reinstall (overwrite)", value: "reinstall" },
{ name: "Cancel", value: "cancel" },
],
},
]);
switch (action) {
case "update":
2025-06-15 01:05:56 -05:00
return await this.performUpdate(config, installDir, state.manifest, spinner);
case "reinstall":
return await this.performReinstall(config, installDir, spinner);
case "cancel":
console.log("Installation cancelled.");
2025-06-12 22:38:24 -05:00
return;
}
}
async handleV3Installation(config, installDir, state, spinner) {
// Ensure modules are initialized
await initializeModules();
spinner.stop();
2025-06-14 15:06:41 -05:00
console.log(
chalk.yellow("\n🔍 Found BMAD v3 installation (bmad-agent/ directory)")
);
console.log(` Directory: ${installDir}`);
const { action } = await inquirer.prompt([
{
type: "list",
name: "action",
message: "What would you like to do?",
choices: [
{ name: "Upgrade from v3 to v4 (recommended)", value: "upgrade" },
{ name: "Install v4 alongside v3", value: "alongside" },
{ name: "Cancel", value: "cancel" },
],
},
]);
switch (action) {
case "upgrade": {
console.log(chalk.cyan("\n📦 Starting v3 to v4 upgrade process..."));
const V3ToV4Upgrader = require("../../upgraders/v3-to-v4-upgrader");
const upgrader = new V3ToV4Upgrader();
return await upgrader.upgrade({ projectPath: installDir });
}
case "alongside":
return await this.performFreshInstall(config, installDir, spinner);
case "cancel":
console.log("Installation cancelled.");
2025-06-12 22:38:24 -05:00
return;
}
}
async handleUnknownInstallation(config, installDir, state, spinner) {
// Ensure modules are initialized
await initializeModules();
spinner.stop();
console.log(chalk.yellow("\n⚠ Directory contains existing files"));
console.log(` Directory: ${installDir}`);
if (state.hasBmadCore) {
console.log(" Found: .bmad-core directory (but no manifest)");
}
if (state.hasOtherFiles) {
console.log(" Found: Other files in directory");
}
const { action } = await inquirer.prompt([
{
type: "list",
name: "action",
message: "What would you like to do?",
choices: [
{ name: "Install anyway (may overwrite files)", value: "force" },
{ name: "Choose different directory", value: "different" },
{ name: "Cancel", value: "cancel" },
],
},
]);
switch (action) {
case "force":
return await this.performFreshInstall(config, installDir, spinner);
case "different": {
const { newDir } = await inquirer.prompt([
{
type: "input",
name: "newDir",
message: "Enter new installation directory:",
default: path.join(path.dirname(installDir), "bmad-project"),
},
]);
config.directory = newDir;
return await this.install(config);
}
case "cancel":
console.log("Installation cancelled.");
return;
}
}
2025-06-14 15:06:41 -05:00
2025-06-15 01:05:56 -05:00
async performUpdate(newConfig, installDir, manifest, spinner) {
spinner.start("Checking for updates...");
try {
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
);
if (modifiedFiles.length > 0) {
2025-06-14 15:06:41 -05:00
spinner.warn("Found modified files");
console.log(chalk.yellow("\nThe following files have been modified:"));
for (const file of modifiedFiles) {
console.log(` - ${file}`);
}
2025-06-14 15:06:41 -05:00
const { action } = await inquirer.prompt([
{
type: "list",
name: "action",
message: "How would you like to proceed?",
choices: [
{ name: "Backup and overwrite modified files", value: "backup" },
{ name: "Skip modified files", value: "skip" },
{ name: "Cancel update", value: "cancel" },
],
},
]);
if (action === "cancel") {
console.log("Update cancelled.");
return;
2025-06-12 22:38:24 -05:00
}
2025-06-14 15:06:41 -05:00
if (action === "backup") {
spinner.start("Backing up modified files...");
for (const file of modifiedFiles) {
const filePath = path.join(installDir, file);
const backupPath = await fileManager.backupFile(filePath);
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
// Perform update by re-running installation
spinner.text = "Updating files...";
2025-06-12 22:38:24 -05:00
const config = {
installType: manifest.install_type,
agent: manifest.agent,
directory: installDir,
2025-06-15 01:05:56 -05:00
ide: newConfig.ide || manifest.ide_setup, // Use new IDE choice if provided
2025-06-12 22:38:24 -05:00
};
2025-06-14 15:06:41 -05:00
await this.performFreshInstall(config, installDir, spinner);
2025-06-12 22:38:24 -05:00
} 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 performReinstall(config, installDir, spinner) {
spinner.start("Reinstalling BMAD Method...");
// Remove existing .bmad-core
const bmadCorePath = path.join(installDir, ".bmad-core");
if (await fileManager.pathExists(bmadCorePath)) {
await fileManager.removeDirectory(bmadCorePath);
}
return await this.performFreshInstall(config, installDir, spinner);
}
showSuccessMessage(config, installDir) {
console.log(chalk.green("\n✓ BMAD Method installed successfully!\n"));
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."));
console.log(
"You can manually configure your IDE using the agent files in:",
installDir
);
}
// 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 (config.expansionPacks && config.expansionPacks.length > 0) {
const packNames = config.expansionPacks.join(", ");
console.log(chalk.green(`✓ Expansion packs installed: ${packNames}`));
}
if (config.includeWebBundles && config.webBundlesDirectory) {
console.log(chalk.green(`✓ Web bundles installed to: ${config.webBundlesDirectory}`));
}
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
if (!config.includeWebBundles) {
console.log(chalk.bold("\n📦 Web Bundles Available:"));
console.log("Pre-built web bundles are available and can be added later:");
console.log(chalk.cyan(" Run the installer again to add them to your project"));
console.log("These bundles work independently and can be shared, moved, or used");
console.log("in other projects as standalone files.");
}
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")
);
}
}
// 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.'
);
const installDir = await this.findInstallation();
if (installDir) {
const config = {
installType: "full",
directory: path.dirname(installDir),
ide: null,
};
return await this.install(config);
}
console.log(chalk.red("No BMAD installation found."));
}
2025-06-12 22:38:24 -05:00
async listAgents() {
// Initialize ES modules
await initializeModules();
2025-06-12 22:38:24 -05:00
const agents = await configLoader.getAvailableAgents();
2025-06-14 15:06:41 -05:00
console.log(chalk.bold("\nAvailable BMAD Agents:\n"));
for (const agent of agents) {
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 listExpansionPacks() {
// Initialize ES modules
await initializeModules();
const expansionPacks = await this.getAvailableExpansionPacks();
console.log(chalk.bold("\nAvailable BMAD Expansion Packs:\n"));
if (expansionPacks.length === 0) {
console.log(chalk.yellow("No expansion packs found."));
return;
}
for (const pack of expansionPacks) {
console.log(chalk.cyan(` ${pack.id.padEnd(20)}`),
`${pack.name} v${pack.version}`);
console.log(chalk.dim(` ${' '.repeat(22)}${pack.description}`));
if (pack.author && pack.author !== 'Unknown') {
console.log(chalk.dim(` ${' '.repeat(22)}by ${pack.author}`));
}
console.log();
}
console.log(
chalk.dim("Install with: npx bmad-method install --full --expansion-packs <id>\n")
);
}
2025-06-12 22:38:24 -05:00
async showStatus() {
// Initialize ES modules
await initializeModules();
2025-06-12 22:38:24 -05:00
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 getAvailableExpansionPacks() {
return configLoader.getAvailableExpansionPacks();
}
async getAvailableTeams() {
return configLoader.getAvailableTeams();
}
async installExpansionPacks(installDir, selectedPacks, spinner) {
if (!selectedPacks || selectedPacks.length === 0) {
return [];
}
const installedFiles = [];
const glob = require('glob');
for (const packId of selectedPacks) {
spinner.text = `Installing expansion pack: ${packId}...`;
try {
const expansionPacks = await this.getAvailableExpansionPacks();
const pack = expansionPacks.find(p => p.id === packId);
if (!pack) {
console.warn(`Expansion pack ${packId} not found, skipping...`);
continue;
}
const expansionPackDir = path.dirname(pack.manifestPath);
// Define the folders to copy from expansion packs to .bmad-core
const foldersToSync = [
'agents',
'agent-teams',
'templates',
'tasks',
'checklists',
'workflows',
'data',
'utils',
'schemas'
];
// Copy each folder if it exists
for (const folder of foldersToSync) {
const sourceFolder = path.join(expansionPackDir, folder);
// Check if folder exists in expansion pack
if (await fileManager.pathExists(sourceFolder)) {
// Get all files in this folder
const files = glob.sync('**/*', {
cwd: sourceFolder,
nodir: true
});
// Copy each file to the destination
for (const file of files) {
const sourcePath = path.join(sourceFolder, file);
const destPath = path.join(installDir, '.bmad-core', folder, file);
if (await fileManager.copyFile(sourcePath, destPath)) {
installedFiles.push(path.join('.bmad-core', folder, file));
}
}
}
}
// Web bundles are now available in the dist/ directory and don't need to be copied
console.log(chalk.green(`✓ Installed expansion pack: ${pack.name}`));
} catch (error) {
console.error(chalk.red(`Failed to install expansion pack ${packId}: ${error.message}`));
}
}
return installedFiles;
}
async installWebBundles(webBundlesDirectory, spinner) {
// Ensure modules are initialized
await initializeModules();
try {
// Find the dist directory in the BMAD installation
const distDir = configLoader.getDistPath();
if (!(await fileManager.pathExists(distDir))) {
console.warn(chalk.yellow('Web bundles not found. Run "npm run build" to generate them.'));
return;
}
// Ensure web bundles directory exists
await fileManager.ensureDirectory(webBundlesDirectory);
// Copy the entire dist directory structure to web bundles directory
await fileManager.copyDirectory(distDir, webBundlesDirectory);
console.log(chalk.green(`✓ Installed web bundles to: ${webBundlesDirectory}`));
} catch (error) {
console.error(chalk.red(`Failed to install web bundles: ${error.message}`));
}
}
2025-06-12 22:38:24 -05:00
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();