BMAD-METHOD/tools/cli/installers/lib/core/options-parser.js

191 lines
4.9 KiB
JavaScript

const { getProfile } = require('../profiles/definitions');
/**
* CLI Options Parser
*
* Parses and normalizes CLI options for non-interactive installation.
* Handles profiles, comma-separated lists, special values, and validation.
*/
/**
* Parse comma-separated list into array
* @param {string|undefined} value - Comma-separated string or undefined
* @returns {string[]|null} Array of trimmed values or null if undefined
*/
function parseList(value) {
if (!value) {
return null;
}
if (typeof value !== 'string') {
return null;
}
return value
.split(',')
.map((item) => item.trim())
.filter((item) => item.length > 0);
}
/**
* Check if value is a special keyword
* @param {string|string[]|null} value - Value to check
* @returns {boolean} True if special keyword (all, none, minimal)
*/
function isSpecialValue(value) {
if (Array.isArray(value) && value.length === 1) {
value = value[0];
}
return value === 'all' || value === 'none' || value === 'minimal';
}
/**
* Separate additive (+) and subtractive (-) modifiers from list
* @param {string[]} list - Array of items, some may have +/- prefix
* @returns {Object} { base: [], add: [], remove: [] }
*/
function separateModifiers(list) {
const result = {
base: [],
add: [],
remove: [],
};
if (!list || !Array.isArray(list)) {
return result;
}
for (const item of list) {
if (item.startsWith('+')) {
result.add.push(item.slice(1));
} else if (item.startsWith('-')) {
result.remove.push(item.slice(1));
} else {
result.base.push(item);
}
}
return result;
}
/**
* Apply modifiers to a base list (additive/subtractive)
* @param {string[]} baseList - Base list of items
* @param {string[]} add - Items to add
* @param {string[]} remove - Items to remove
* @returns {string[]} Modified list
*/
function applyModifiers(baseList, add = [], remove = []) {
let result = [...baseList];
// Add items
for (const item of add) {
if (!result.includes(item)) {
result.push(item);
}
}
// Remove items
for (const item of remove) {
result = result.filter((i) => i !== item);
}
return result;
}
/**
* Parse and normalize CLI options
* @param {Object} cliOptions - Raw CLI options from commander
* @returns {Object} Normalized options
*/
function parseOptions(cliOptions) {
const normalized = {
nonInteractive: cliOptions.nonInteractive || false,
userName: cliOptions.userName,
skillLevel: cliOptions.skillLevel,
outputFolder: cliOptions.outputFolder,
communicationLanguage: cliOptions.communicationLanguage,
documentLanguage: cliOptions.documentLanguage,
modules: parseList(cliOptions.modules),
agents: parseList(cliOptions.agents),
workflows: parseList(cliOptions.workflows),
team: cliOptions.team,
profile: cliOptions.profile,
};
// Expand profile if provided
if (normalized.profile) {
const profile = getProfile(normalized.profile);
if (!profile) {
throw new Error(`Unknown profile: ${normalized.profile}. Valid profiles: minimal, full, solo-dev, team`);
}
// Profile provides defaults, but CLI options override
normalized.profileModules = profile.modules;
normalized.profileAgents = profile.agents;
normalized.profileWorkflows = profile.workflows;
// If no explicit modules/agents/workflows, use profile values
if (!normalized.modules) {
normalized.modules = profile.modules;
}
if (!normalized.agents) {
normalized.agents = profile.agents;
}
if (!normalized.workflows) {
normalized.workflows = profile.workflows;
}
}
return normalized;
}
/**
* Validate parsed options for conflicts and errors
* @param {Object} options - Parsed options
* @returns {Object} { valid: boolean, errors: string[] }
*/
function validateOptions(options) {
const errors = [];
// Validate skill level
if (options.skillLevel) {
const validLevels = ['beginner', 'intermediate', 'advanced'];
if (!validLevels.includes(options.skillLevel.toLowerCase())) {
errors.push(`Invalid skill level: ${options.skillLevel}. Valid values: beginner, intermediate, advanced`);
}
}
// Validate profile
if (options.profile) {
const validProfiles = ['minimal', 'full', 'solo-dev', 'team'];
if (!validProfiles.includes(options.profile.toLowerCase())) {
errors.push(`Invalid profile: ${options.profile}. Valid values: minimal, full, solo-dev, team`);
}
}
// Check for empty selections
if (options.agents && Array.isArray(options.agents) && options.agents.length === 0) {
errors.push('Agents list cannot be empty');
}
if (options.workflows && Array.isArray(options.workflows) && options.workflows.length === 0) {
errors.push('Workflows list cannot be empty');
}
return {
valid: errors.length === 0,
errors,
};
}
module.exports = {
parseList,
isSpecialValue,
separateModifiers,
applyModifiers,
parseOptions,
validateOptions,
};