Merge c18944f3ff into 5b80649d3a
This commit is contained in:
commit
a934157b49
|
|
@ -30,6 +30,14 @@ npx bmad-method install
|
|||
|
||||
Follow the installer prompts, then open your AI IDE (Claude Code, Cursor, Windsurf, etc.) in the project folder.
|
||||
|
||||
**Non-Interactive Installation**: For CI/CD pipelines or automated deployments, use command-line flags:
|
||||
|
||||
```bash
|
||||
npx bmad-method install --directory /path/to/project --modules bmm --tools claude-code --yes
|
||||
```
|
||||
|
||||
See [Non-Interactive Installation Guide](docs/non-interactive-installation.md) for all available options.
|
||||
|
||||
> **Not sure what to do?** Run `/bmad-help` — it tells you exactly what's next and what's optional. You can also ask it questions like:
|
||||
|
||||
- `/bmad-help How should I build a web app for my TShirt Business that can scale to millions?`
|
||||
|
|
|
|||
|
|
@ -0,0 +1,314 @@
|
|||
---
|
||||
title: Non-Interactive Installation
|
||||
description: Install BMAD using command-line flags for CI/CD pipelines and automated deployments
|
||||
---
|
||||
|
||||
# Non-Interactive Installation
|
||||
|
||||
BMAD now supports non-interactive installation through command-line flags. This is particularly useful for:
|
||||
|
||||
- Automated deployments and CI/CD pipelines
|
||||
- Scripted installations
|
||||
- Batch installations across multiple projects
|
||||
- Quick installations with known configurations
|
||||
|
||||
## Installation Modes
|
||||
|
||||
### 1. Fully Interactive (Default)
|
||||
|
||||
Run without any flags to use the traditional interactive prompts:
|
||||
|
||||
```bash
|
||||
npx bmad-method install
|
||||
```
|
||||
|
||||
### 2. Fully Non-Interactive
|
||||
|
||||
Provide all required flags to skip all prompts:
|
||||
|
||||
```bash
|
||||
npx bmad-method install \
|
||||
--directory /path/to/project \
|
||||
--modules bmm,bmb \
|
||||
--tools claude-code,cursor \
|
||||
--user-name "John Doe" \
|
||||
--communication-language English \
|
||||
--document-output-language English \
|
||||
--output-folder _bmad-output
|
||||
```
|
||||
|
||||
### 3. Semi-Interactive (Graceful Fallback)
|
||||
|
||||
Provide some flags and let BMAD prompt for the rest:
|
||||
|
||||
```bash
|
||||
npx bmad-method install \
|
||||
--directory /path/to/project \
|
||||
--modules bmm
|
||||
```
|
||||
|
||||
In this case, BMAD will:
|
||||
- Use the provided directory and modules
|
||||
- Prompt for tool selection
|
||||
- Prompt for core configuration
|
||||
|
||||
### 4. Quick Install with Defaults
|
||||
|
||||
Use the `-y` or `--yes` flag to accept all defaults:
|
||||
|
||||
```bash
|
||||
npx bmad-method install --yes
|
||||
```
|
||||
|
||||
This will:
|
||||
- Install to the current directory
|
||||
- Skip custom content prompts
|
||||
- Use default values for all configuration
|
||||
- Use previously configured tools (or skip tool configuration if none exist)
|
||||
|
||||
### 5. Install Without Tools
|
||||
|
||||
To skip tool/IDE configuration entirely:
|
||||
|
||||
**Option 1: Use --tools none**
|
||||
```bash
|
||||
npx bmad-method install --directory ~/myapp --modules bmm --tools none
|
||||
```
|
||||
|
||||
**Option 2: Use --yes flag (if no tools were previously configured)**
|
||||
```bash
|
||||
npx bmad-method install --yes
|
||||
```
|
||||
|
||||
**Option 3: Omit --tools and select "None" in the interactive prompt**
|
||||
```bash
|
||||
npx bmad-method install --directory ~/myapp --modules bmm
|
||||
# Then select "⚠ None - I am not installing any tools" when prompted
|
||||
```
|
||||
|
||||
## Available Flags
|
||||
|
||||
### Installation Options
|
||||
|
||||
| Flag | Description | Example |
|
||||
|------|-------------|---------|
|
||||
| `--directory <path>` | Installation directory | `--directory ~/projects/myapp` |
|
||||
| `--modules <modules>` | Comma-separated module IDs | `--modules bmm,bmb` |
|
||||
| `--tools <tools>` | Comma-separated tool/IDE IDs (use "none" to skip) | `--tools claude-code,cursor` or `--tools none` |
|
||||
| `--custom-content <paths>` | Comma-separated paths to custom modules | `--custom-content ~/my-module,~/another-module` |
|
||||
| `--action <type>` | Action for existing installations | `--action quick-update` |
|
||||
|
||||
### Core Configuration
|
||||
|
||||
| Flag | Description | Default |
|
||||
|------|-------------|---------|
|
||||
| `--user-name <name>` | Name for agents to use | System username |
|
||||
| `--communication-language <lang>` | Agent communication language | English |
|
||||
| `--document-output-language <lang>` | Document output language | English |
|
||||
| `--output-folder <path>` | Output folder path | _bmad-output |
|
||||
|
||||
### Other Options
|
||||
|
||||
| Flag | Description |
|
||||
|------|-------------|
|
||||
| `-y, --yes` | Accept all defaults and skip prompts |
|
||||
| `-d, --debug` | Enable debug output for manifest generation |
|
||||
|
||||
## Action Types
|
||||
|
||||
When working with existing installations, use the `--action` flag:
|
||||
|
||||
- `install` - Fresh installation (default for new directories)
|
||||
- `update` - Modify existing installation (change modules/config)
|
||||
- `quick-update` - Refresh installation without changing configuration
|
||||
- `compile-agents` - Recompile agents with customizations only
|
||||
|
||||
Example:
|
||||
|
||||
```bash
|
||||
npx bmad-method install --action quick-update
|
||||
```
|
||||
|
||||
## Module IDs
|
||||
|
||||
Available module IDs for the `--modules` flag:
|
||||
|
||||
### Core Modules
|
||||
- `bmm` - BMad Method Master
|
||||
- `bmb` - BMad Builder
|
||||
|
||||
### External Modules
|
||||
Check the [BMad registry](https://github.com/bmad-code-org) for available external modules.
|
||||
|
||||
## Tool/IDE IDs
|
||||
|
||||
Available tool IDs for the `--tools` flag:
|
||||
|
||||
- `claude-code` - Claude Code CLI
|
||||
- `cursor` - Cursor IDE
|
||||
- `windsurf` - Windsurf IDE
|
||||
- `vscode` - Visual Studio Code
|
||||
- `jetbrains` - JetBrains IDEs
|
||||
- And more...
|
||||
|
||||
Run the interactive installer once to see all available tools.
|
||||
|
||||
## Examples
|
||||
|
||||
### Basic Installation
|
||||
|
||||
Install BMM module with Claude Code:
|
||||
|
||||
```bash
|
||||
npx bmad-method install \
|
||||
--directory ~/projects/myapp \
|
||||
--modules bmm \
|
||||
--tools claude-code \
|
||||
--user-name "Development Team"
|
||||
```
|
||||
|
||||
### Installation Without Tools
|
||||
|
||||
Install without configuring any tools/IDEs:
|
||||
|
||||
```bash
|
||||
npx bmad-method install \
|
||||
--directory ~/projects/myapp \
|
||||
--modules bmm \
|
||||
--tools none \
|
||||
--user-name "Development Team"
|
||||
```
|
||||
|
||||
### Full Installation with Multiple Modules
|
||||
|
||||
```bash
|
||||
npx bmad-method install \
|
||||
--directory ~/projects/myapp \
|
||||
--modules bmm,bmb \
|
||||
--tools claude-code,cursor \
|
||||
--user-name "John Doe" \
|
||||
--communication-language English \
|
||||
--document-output-language English \
|
||||
--output-folder _output
|
||||
```
|
||||
|
||||
### Update Existing Installation
|
||||
|
||||
```bash
|
||||
npx bmad-method install \
|
||||
--directory ~/projects/myapp \
|
||||
--action update \
|
||||
--modules bmm,bmb,custom-module
|
||||
```
|
||||
|
||||
### Quick Update (Preserve Settings)
|
||||
|
||||
```bash
|
||||
npx bmad-method install \
|
||||
--directory ~/projects/myapp \
|
||||
--action quick-update
|
||||
```
|
||||
|
||||
### Installation with Custom Content
|
||||
|
||||
```bash
|
||||
npx bmad-method install \
|
||||
--directory ~/projects/myapp \
|
||||
--modules bmm \
|
||||
--custom-content ~/my-custom-module,~/another-module \
|
||||
--tools claude-code
|
||||
```
|
||||
|
||||
### CI/CD Pipeline Installation
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# install-bmad.sh
|
||||
|
||||
npx bmad-method install \
|
||||
--directory "${GITHUB_WORKSPACE}" \
|
||||
--modules bmm \
|
||||
--tools claude-code \
|
||||
--user-name "CI Bot" \
|
||||
--communication-language English \
|
||||
--document-output-language English \
|
||||
--output-folder _bmad-output \
|
||||
--yes
|
||||
```
|
||||
|
||||
## Environment-Specific Installations
|
||||
|
||||
### Development Environment
|
||||
|
||||
```bash
|
||||
npx bmad-method install \
|
||||
--directory . \
|
||||
--modules bmm,bmb \
|
||||
--tools claude-code,cursor \
|
||||
--user-name "${USER}"
|
||||
```
|
||||
|
||||
### Production Environment
|
||||
|
||||
```bash
|
||||
npx bmad-method install \
|
||||
--directory /opt/app \
|
||||
--modules bmm \
|
||||
--tools claude-code \
|
||||
--user-name "Production Team" \
|
||||
--output-folder /var/bmad-output
|
||||
```
|
||||
|
||||
## Validation and Error Handling
|
||||
|
||||
BMAD validates all provided flags:
|
||||
|
||||
- **Directory**: Must be a valid path with write permissions
|
||||
- **Modules**: Will warn about invalid module IDs (but won't fail)
|
||||
- **Tools**: Will warn about invalid tool IDs (but won't fail)
|
||||
- **Custom Content**: Each path must contain a valid `module.yaml` file
|
||||
- **Action**: Must be one of: install, update, quick-update, compile-agents
|
||||
|
||||
Invalid values will either:
|
||||
1. Show an error and exit (for critical options like directory)
|
||||
2. Show a warning and skip (for optional items like custom content)
|
||||
3. Fall back to interactive prompts (for missing required values)
|
||||
|
||||
## Tips and Best Practices
|
||||
|
||||
1. **Use absolute paths** for `--directory` to avoid ambiguity
|
||||
2. **Test flags locally** before using in CI/CD pipelines
|
||||
3. **Combine with `-y`** for truly unattended installations
|
||||
4. **Check module availability** by running the interactive installer once
|
||||
5. **Use `--debug`** flag if you encounter issues during installation
|
||||
6. **Skip tool configuration** with `--tools none` for server/CI environments where IDEs aren't needed
|
||||
7. **Partial flags are OK** - Omit flags and let BMAD prompt for missing values interactively
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Installation fails with "Invalid directory"
|
||||
|
||||
Check that:
|
||||
- The directory path exists or its parent exists
|
||||
- You have write permissions
|
||||
- The path is absolute or correctly relative to current directory
|
||||
|
||||
### Module not found
|
||||
|
||||
- Verify the module ID is correct (check available modules in interactive mode)
|
||||
- External modules may need to be available in the registry
|
||||
|
||||
### Custom content path invalid
|
||||
|
||||
Ensure each custom content path:
|
||||
- Points to a directory
|
||||
- Contains a `module.yaml` file in the root
|
||||
- Has a `code` field in the `module.yaml`
|
||||
|
||||
## Feedback and Issues
|
||||
|
||||
If you encounter any issues with non-interactive installation:
|
||||
|
||||
1. Run with `--debug` flag for detailed output
|
||||
2. Try the interactive mode to verify the issue
|
||||
3. Report issues on GitHub: <https://github.com/bmad-code-org/BMAD-METHOD/issues>
|
||||
|
|
@ -9,7 +9,22 @@ const ui = new UI();
|
|||
module.exports = {
|
||||
command: 'install',
|
||||
description: 'Install BMAD Core agents and tools',
|
||||
options: [['-d, --debug', 'Enable debug output for manifest generation']],
|
||||
options: [
|
||||
['-d, --debug', 'Enable debug output for manifest generation'],
|
||||
['--directory <path>', 'Installation directory (default: current directory)'],
|
||||
['--modules <modules>', 'Comma-separated list of module IDs to install (e.g., "bmm,bmb")'],
|
||||
[
|
||||
'--tools <tools>',
|
||||
'Comma-separated list of tool/IDE IDs to configure (e.g., "claude-code,cursor"). Use "none" to skip tool configuration.',
|
||||
],
|
||||
['--custom-content <paths>', 'Comma-separated list of paths to custom modules/agents/workflows'],
|
||||
['--action <type>', 'Action type for existing installations: install, update, quick-update, or compile-agents'],
|
||||
['--user-name <name>', 'Name for agents to use (default: system username)'],
|
||||
['--communication-language <lang>', 'Language for agent communication (default: English)'],
|
||||
['--document-output-language <lang>', 'Language for document output (default: English)'],
|
||||
['--output-folder <path>', 'Output folder path relative to project root (default: _bmad-output)'],
|
||||
['-y, --yes', 'Accept all defaults and skip prompts where possible'],
|
||||
],
|
||||
action: async (options) => {
|
||||
try {
|
||||
// Set debug flag as environment variable for all components
|
||||
|
|
@ -18,7 +33,7 @@ module.exports = {
|
|||
console.log(chalk.cyan('Debug mode enabled\n'));
|
||||
}
|
||||
|
||||
const config = await ui.promptInstall();
|
||||
const config = await ui.promptInstall(options);
|
||||
|
||||
// Handle cancel
|
||||
if (config.actionType === 'cancel') {
|
||||
|
|
|
|||
|
|
@ -26,9 +26,10 @@ const choiceUtils = { Separator };
|
|||
class UI {
|
||||
/**
|
||||
* Prompt for installation configuration
|
||||
* @param {Object} options - Command-line options from install command
|
||||
* @returns {Object} Installation configuration
|
||||
*/
|
||||
async promptInstall() {
|
||||
async promptInstall(options = {}) {
|
||||
CLIUtils.displayLogo();
|
||||
|
||||
// Display version-specific start message from install-messages.yaml
|
||||
|
|
@ -36,7 +37,20 @@ class UI {
|
|||
const messageLoader = new MessageLoader();
|
||||
messageLoader.displayStartMessage();
|
||||
|
||||
const confirmedDirectory = await this.getConfirmedDirectory();
|
||||
// Get directory from options or prompt
|
||||
let confirmedDirectory;
|
||||
if (options.directory) {
|
||||
// Use provided directory from command-line
|
||||
const expandedDir = this.expandUserPath(options.directory);
|
||||
const validation = this.validateDirectorySync(expandedDir);
|
||||
if (validation !== true) {
|
||||
throw new Error(`Invalid directory: ${validation}`);
|
||||
}
|
||||
confirmedDirectory = expandedDir;
|
||||
console.log(chalk.cyan('Using directory from command-line:'), chalk.bold(confirmedDirectory));
|
||||
} else {
|
||||
confirmedDirectory = await this.getConfirmedDirectory();
|
||||
}
|
||||
|
||||
// Preflight: Check for legacy BMAD v4 footprints immediately after getting directory
|
||||
const { Detector } = require('../installers/lib/core/detector');
|
||||
|
|
@ -218,11 +232,21 @@ class UI {
|
|||
// Common actions
|
||||
choices.push({ name: 'Modify BMAD Installation', value: 'update' });
|
||||
|
||||
// Check if action is provided via command-line
|
||||
if (options.action) {
|
||||
const validActions = choices.map((c) => c.value);
|
||||
if (!validActions.includes(options.action)) {
|
||||
throw new Error(`Invalid action: ${options.action}. Valid actions: ${validActions.join(', ')}`);
|
||||
}
|
||||
actionType = options.action;
|
||||
console.log(chalk.cyan('Using action from command-line:'), chalk.bold(actionType));
|
||||
} else {
|
||||
actionType = await prompts.select({
|
||||
message: 'How would you like to proceed?',
|
||||
choices: choices,
|
||||
default: choices[0].value,
|
||||
});
|
||||
}
|
||||
|
||||
// Handle quick update separately
|
||||
if (actionType === 'quick-update') {
|
||||
|
|
@ -253,16 +277,79 @@ class UI {
|
|||
console.log(chalk.dim(` Found existing modules: ${[...installedModuleIds].join(', ')}`));
|
||||
|
||||
// Unified module selection - all modules in one grouped multiselect
|
||||
let selectedModules = await this.selectAllModules(installedModuleIds);
|
||||
let selectedModules;
|
||||
if (options.modules) {
|
||||
// Use modules from command-line
|
||||
selectedModules = options.modules
|
||||
.split(',')
|
||||
.map((m) => m.trim())
|
||||
.filter(Boolean);
|
||||
console.log(chalk.cyan('Using modules from command-line:'), chalk.bold(selectedModules.join(', ')));
|
||||
} else {
|
||||
selectedModules = await this.selectAllModules(installedModuleIds);
|
||||
}
|
||||
|
||||
// After module selection, ask about custom modules
|
||||
console.log('');
|
||||
let customModuleResult = { selectedCustomModules: [], customContentConfig: { hasCustomContent: false } };
|
||||
|
||||
if (options.customContent) {
|
||||
// Use custom content from command-line
|
||||
const paths = options.customContent
|
||||
.split(',')
|
||||
.map((p) => p.trim())
|
||||
.filter(Boolean);
|
||||
console.log(chalk.cyan('Using custom content from command-line:'), chalk.bold(paths.join(', ')));
|
||||
|
||||
// Build custom content config similar to promptCustomContentSource
|
||||
const customPaths = [];
|
||||
const selectedModuleIds = [];
|
||||
|
||||
for (const customPath of paths) {
|
||||
const expandedPath = this.expandUserPath(customPath);
|
||||
const validation = this.validateCustomContentPathSync(expandedPath);
|
||||
if (validation !== true) {
|
||||
console.log(chalk.yellow(`⚠️ Skipping invalid custom content path: ${customPath} - ${validation}`));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Read module metadata
|
||||
let moduleMeta;
|
||||
try {
|
||||
const moduleYamlPath = path.join(expandedPath, 'module.yaml');
|
||||
const moduleYaml = await fs.readFile(moduleYamlPath, 'utf-8');
|
||||
const yaml = require('yaml');
|
||||
moduleMeta = yaml.parse(moduleYaml);
|
||||
} catch (error) {
|
||||
console.log(chalk.yellow(`⚠️ Skipping custom content path: ${customPath} - failed to read module.yaml: ${error.message}`));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!moduleMeta.code) {
|
||||
console.log(chalk.yellow(`⚠️ Skipping custom content path: ${customPath} - module.yaml missing 'code' field`));
|
||||
continue;
|
||||
}
|
||||
|
||||
customPaths.push(expandedPath);
|
||||
selectedModuleIds.push(moduleMeta.code);
|
||||
}
|
||||
|
||||
if (customPaths.length > 0) {
|
||||
customModuleResult = {
|
||||
selectedCustomModules: selectedModuleIds,
|
||||
customContentConfig: {
|
||||
hasCustomContent: true,
|
||||
paths: customPaths,
|
||||
selectedModuleIds: selectedModuleIds,
|
||||
},
|
||||
};
|
||||
}
|
||||
} else {
|
||||
const changeCustomModules = await prompts.confirm({
|
||||
message: 'Modify custom modules, agents, or workflows?',
|
||||
default: false,
|
||||
});
|
||||
|
||||
let customModuleResult = { selectedCustomModules: [], customContentConfig: { hasCustomContent: false } };
|
||||
if (changeCustomModules) {
|
||||
customModuleResult = await this.handleCustomModulesInModifyFlow(confirmedDirectory, selectedModules);
|
||||
} else {
|
||||
|
|
@ -281,6 +368,7 @@ class UI {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Merge any selected custom modules
|
||||
if (customModuleResult.selectedCustomModules.length > 0) {
|
||||
|
|
@ -288,9 +376,9 @@ class UI {
|
|||
}
|
||||
|
||||
// Get tool selection
|
||||
const toolSelection = await this.promptToolSelection(confirmedDirectory);
|
||||
const toolSelection = await this.promptToolSelection(confirmedDirectory, options);
|
||||
|
||||
const coreConfig = await this.collectCoreConfig(confirmedDirectory);
|
||||
const coreConfig = await this.collectCoreConfig(confirmedDirectory, options);
|
||||
|
||||
return {
|
||||
actionType: 'update',
|
||||
|
|
@ -309,9 +397,68 @@ class UI {
|
|||
const { installedModuleIds } = await this.getExistingInstallation(confirmedDirectory);
|
||||
|
||||
// Unified module selection - all modules in one grouped multiselect
|
||||
let selectedModules = await this.selectAllModules(installedModuleIds);
|
||||
let selectedModules;
|
||||
if (options.modules) {
|
||||
// Use modules from command-line
|
||||
selectedModules = options.modules
|
||||
.split(',')
|
||||
.map((m) => m.trim())
|
||||
.filter(Boolean);
|
||||
console.log(chalk.cyan('Using modules from command-line:'), chalk.bold(selectedModules.join(', ')));
|
||||
} else {
|
||||
selectedModules = await this.selectAllModules(installedModuleIds);
|
||||
}
|
||||
|
||||
// Ask about custom content (local modules/agents/workflows)
|
||||
if (options.customContent) {
|
||||
// Use custom content from command-line
|
||||
const paths = options.customContent
|
||||
.split(',')
|
||||
.map((p) => p.trim())
|
||||
.filter(Boolean);
|
||||
console.log(chalk.cyan('Using custom content from command-line:'), chalk.bold(paths.join(', ')));
|
||||
|
||||
// Build custom content config similar to promptCustomContentSource
|
||||
const customPaths = [];
|
||||
const selectedModuleIds = [];
|
||||
|
||||
for (const customPath of paths) {
|
||||
const expandedPath = this.expandUserPath(customPath);
|
||||
const validation = this.validateCustomContentPathSync(expandedPath);
|
||||
if (validation !== true) {
|
||||
console.log(chalk.yellow(`⚠️ Skipping invalid custom content path: ${customPath} - ${validation}`));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Read module metadata
|
||||
let moduleMeta;
|
||||
try {
|
||||
const moduleYamlPath = path.join(expandedPath, 'module.yaml');
|
||||
const moduleYaml = await fs.readFile(moduleYamlPath, 'utf-8');
|
||||
const yaml = require('yaml');
|
||||
moduleMeta = yaml.parse(moduleYaml);
|
||||
} catch (error) {
|
||||
console.log(chalk.yellow(`⚠️ Skipping custom content path: ${customPath} - failed to read module.yaml: ${error.message}`));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!moduleMeta.code) {
|
||||
console.log(chalk.yellow(`⚠️ Skipping custom content path: ${customPath} - module.yaml missing 'code' field`));
|
||||
continue;
|
||||
}
|
||||
|
||||
customPaths.push(expandedPath);
|
||||
selectedModuleIds.push(moduleMeta.code);
|
||||
}
|
||||
|
||||
if (customPaths.length > 0) {
|
||||
customContentConfig = {
|
||||
hasCustomContent: true,
|
||||
paths: customPaths,
|
||||
selectedModuleIds: selectedModuleIds,
|
||||
};
|
||||
}
|
||||
} else if (!options.yes) {
|
||||
const wantsCustomContent = await prompts.confirm({
|
||||
message: 'Add custom modules, agents, or workflows from your computer?',
|
||||
default: false,
|
||||
|
|
@ -320,6 +467,7 @@ class UI {
|
|||
if (wantsCustomContent) {
|
||||
customContentConfig = await this.promptCustomContentSource();
|
||||
}
|
||||
}
|
||||
|
||||
// Add custom content modules if any were selected
|
||||
if (customContentConfig && customContentConfig.selectedModuleIds) {
|
||||
|
|
@ -327,8 +475,8 @@ class UI {
|
|||
}
|
||||
|
||||
selectedModules = selectedModules.filter((m) => m !== 'core');
|
||||
let toolSelection = await this.promptToolSelection(confirmedDirectory);
|
||||
const coreConfig = await this.collectCoreConfig(confirmedDirectory);
|
||||
let toolSelection = await this.promptToolSelection(confirmedDirectory, options);
|
||||
const coreConfig = await this.collectCoreConfig(confirmedDirectory, options);
|
||||
|
||||
return {
|
||||
actionType: 'install',
|
||||
|
|
@ -345,9 +493,10 @@ class UI {
|
|||
/**
|
||||
* Prompt for tool/IDE selection (called after module configuration)
|
||||
* @param {string} projectDir - Project directory to check for existing IDEs
|
||||
* @param {Object} options - Command-line options
|
||||
* @returns {Object} Tool configuration
|
||||
*/
|
||||
async promptToolSelection(projectDir) {
|
||||
async promptToolSelection(projectDir, options = {}) {
|
||||
// Check for existing configured IDEs - use findBmadDir to detect custom folder names
|
||||
const { Detector } = require('../installers/lib/core/detector');
|
||||
const { Installer } = require('../installers/lib/core/installer');
|
||||
|
|
@ -433,6 +582,29 @@ class UI {
|
|||
|
||||
let selectedIdes = [];
|
||||
|
||||
// Check if tools are provided via command-line
|
||||
if (options.tools) {
|
||||
// Check for explicit "none" value to skip tool installation
|
||||
if (options.tools.toLowerCase() === 'none') {
|
||||
console.log(chalk.cyan('Skipping tool configuration (--tools none)'));
|
||||
selectedIdes = [];
|
||||
} else {
|
||||
selectedIdes = options.tools
|
||||
.split(',')
|
||||
.map((t) => t.trim())
|
||||
.filter(Boolean);
|
||||
console.log(chalk.cyan('Using tools from command-line:'), chalk.bold(selectedIdes.join(', ')));
|
||||
}
|
||||
} else if (options.yes) {
|
||||
// If --yes flag is set, skip tool prompt and use previously configured tools or empty
|
||||
if (initialValues.length > 0) {
|
||||
selectedIdes = initialValues;
|
||||
console.log(chalk.cyan('Using previously configured tools (--yes flag):'), chalk.bold(selectedIdes.join(', ')));
|
||||
} else {
|
||||
console.log(chalk.cyan('Skipping tool configuration (--yes flag, no previous tools)'));
|
||||
selectedIdes = [];
|
||||
}
|
||||
} else {
|
||||
selectedIdes = await prompts.groupMultiselect({
|
||||
message: `Select tools to configure ${chalk.dim('(↑/↓ navigates, SPACE toggles, ENTER to confirm)')}:`,
|
||||
options: groupedOptions,
|
||||
|
|
@ -440,6 +612,7 @@ class UI {
|
|||
required: true,
|
||||
selectableGroups: false,
|
||||
});
|
||||
}
|
||||
|
||||
// If user selected both "__NONE__" and other tools, honor the "None" choice
|
||||
if (selectedIdes && selectedIdes.includes('__NONE__') && selectedIdes.length > 1) {
|
||||
|
|
@ -542,15 +715,75 @@ class UI {
|
|||
/**
|
||||
* Collect core configuration
|
||||
* @param {string} directory - Installation directory
|
||||
* @param {Object} options - Command-line options
|
||||
* @returns {Object} Core configuration
|
||||
*/
|
||||
async collectCoreConfig(directory) {
|
||||
async collectCoreConfig(directory, options = {}) {
|
||||
const { ConfigCollector } = require('../installers/lib/core/config-collector');
|
||||
const configCollector = new ConfigCollector();
|
||||
|
||||
// If options are provided, set them directly
|
||||
if (options.userName || options.communicationLanguage || options.documentOutputLanguage || options.outputFolder) {
|
||||
const coreConfig = {};
|
||||
if (options.userName) {
|
||||
coreConfig.user_name = options.userName;
|
||||
console.log(chalk.cyan('Using user name from command-line:'), chalk.bold(options.userName));
|
||||
}
|
||||
if (options.communicationLanguage) {
|
||||
coreConfig.communication_language = options.communicationLanguage;
|
||||
console.log(chalk.cyan('Using communication language from command-line:'), chalk.bold(options.communicationLanguage));
|
||||
}
|
||||
if (options.documentOutputLanguage) {
|
||||
coreConfig.document_output_language = options.documentOutputLanguage;
|
||||
console.log(chalk.cyan('Using document output language from command-line:'), chalk.bold(options.documentOutputLanguage));
|
||||
}
|
||||
if (options.outputFolder) {
|
||||
coreConfig.output_folder = options.outputFolder;
|
||||
console.log(chalk.cyan('Using output folder from command-line:'), chalk.bold(options.outputFolder));
|
||||
}
|
||||
|
||||
// Load existing config to merge with provided options
|
||||
await configCollector.loadExistingConfig(directory);
|
||||
|
||||
// Merge provided options with existing config (or defaults)
|
||||
const existingConfig = configCollector.collectedConfig.core || {};
|
||||
configCollector.collectedConfig.core = { ...existingConfig, ...coreConfig };
|
||||
|
||||
// If not all options are provided, collect the missing ones interactively (unless --yes flag)
|
||||
if (
|
||||
!options.yes &&
|
||||
(!options.userName || !options.communicationLanguage || !options.documentOutputLanguage || !options.outputFolder)
|
||||
) {
|
||||
await configCollector.collectModuleConfig('core', directory, false, true);
|
||||
}
|
||||
} else if (options.yes) {
|
||||
// Use all defaults when --yes flag is set
|
||||
await configCollector.loadExistingConfig(directory);
|
||||
const existingConfig = configCollector.collectedConfig.core || {};
|
||||
|
||||
// If no existing config, use defaults
|
||||
if (Object.keys(existingConfig).length === 0) {
|
||||
let safeUsername;
|
||||
try {
|
||||
safeUsername = os.userInfo().username;
|
||||
} catch {
|
||||
safeUsername = process.env.USER || process.env.USERNAME || 'User';
|
||||
}
|
||||
const defaultUsername = safeUsername.charAt(0).toUpperCase() + safeUsername.slice(1);
|
||||
configCollector.collectedConfig.core = {
|
||||
user_name: defaultUsername,
|
||||
communication_language: 'English',
|
||||
document_output_language: 'English',
|
||||
output_folder: '_bmad-output',
|
||||
};
|
||||
console.log(chalk.cyan('Using default configuration (--yes flag)'));
|
||||
}
|
||||
} else {
|
||||
// Load existing configs first if they exist
|
||||
await configCollector.loadExistingConfig(directory);
|
||||
// Now collect with existing values as defaults (false = don't skip loading, true = skip completion message)
|
||||
await configCollector.collectModuleConfig('core', directory, false, true);
|
||||
}
|
||||
|
||||
const coreConfig = configCollector.collectedConfig.core;
|
||||
// Ensure we always have a core config object, even if empty
|
||||
|
|
|
|||
Loading…
Reference in New Issue