Compare commits

...

7 Commits

Author SHA1 Message Date
Jonah Schulte 6c6096e01b
Merge 5c0dfd85ad into f3f606a9ce 2026-03-17 18:26:47 -04:00
Jonah Schulte 5c0dfd85ad
Merge branch 'main' into fix/non-interactive-install-defaults 2026-03-13 20:18:01 -04:00
Jonah Schulte 7422d3a0a3 fix(cli): address PR review feedback for non-interactive install defaults
- Validate cwd with validateDirectorySync in --yes path
- Fix JSDoc placement: move collectCoreConfig docs above its method
- Read user_name default from module.yaml, fall back to OS username
- Harden YAML defaults with type/empty-string normalization
- Log warning on module.yaml load failure instead of silent catch
- Fix backfill predicate to detect unresolved placeholders like {output_folder}
- Always merge defaults into existing config in --yes mode
2026-03-13 16:53:45 -04:00
Jonah Schulte d2e7158c72 Merge branch 'fix/non-interactive-install-defaults' of github.com:jschulte/BMAD-METHOD into fix/non-interactive-install-defaults 2026-03-13 16:49:15 -04:00
Jonah Schulte ac04378a1a Merge remote-tracking branch 'upstream/main' into fix/non-interactive-install-defaults 2026-03-13 16:48:46 -04:00
Jonah Schulte fe2cbe9a78
Merge branch 'main' into fix/non-interactive-install-defaults 2026-03-13 16:29:57 -04:00
Jonah Schulte 1cdc7f4a48 fix(cli): resolve non-interactive install defaults for directory and output_folder
When using --yes with partial CLI options (e.g. --user-name without
--output-folder), the installer had two bugs:

1. Still prompted for directory instead of defaulting to cwd
2. Left {output_folder} unresolved in module configs, creating a literal
   "{output_folder}" directory instead of "_bmad-output"

Extract getDefaultCoreConfig() that reads defaults from
src/core/module.yaml (single source of truth) and use it to backfill
missing fields when --yes skips interactive prompts.
2026-03-13 16:27:26 -04:00
1 changed files with 69 additions and 15 deletions

View File

@ -47,6 +47,15 @@ class UI {
} }
confirmedDirectory = expandedDir; confirmedDirectory = expandedDir;
await prompts.log.info(`Using directory from command-line: ${confirmedDirectory}`); await prompts.log.info(`Using directory from command-line: ${confirmedDirectory}`);
} else if (options.yes) {
// Default to current directory when --yes flag is set
const cwd = process.cwd();
const validation = this.validateDirectorySync(cwd);
if (validation) {
throw new Error(`Invalid current directory: ${validation}`);
}
confirmedDirectory = cwd;
await prompts.log.info(`Using current directory (--yes flag): ${confirmedDirectory}`);
} else { } else {
confirmedDirectory = await this.getConfirmedDirectory(); confirmedDirectory = await this.getConfirmedDirectory();
} }
@ -842,6 +851,45 @@ class UI {
return { existingInstall, installedModuleIds, bmadDir }; return { existingInstall, installedModuleIds, bmadDir };
} }
/**
* Get default core config values by reading from src/core/module.yaml
* @returns {Object} Default core config with user_name, communication_language, document_output_language, output_folder
*/
getDefaultCoreConfig() {
const { getModulePath } = require('./project-root');
const yaml = require('yaml');
let safeUsername;
try {
safeUsername = os.userInfo().username;
} catch {
safeUsername = process.env.USER || process.env.USERNAME || 'User';
}
const osUsername = safeUsername.charAt(0).toUpperCase() + safeUsername.slice(1);
const norm = (value, fallback) => (typeof value === 'string' && value.trim() !== '' ? value.trim() : fallback);
// Read defaults from core module.yaml (single source of truth)
try {
const moduleYamlPath = path.join(getModulePath('core'), 'module.yaml');
const moduleConfig = yaml.parse(fs.readFileSync(moduleYamlPath, 'utf8'));
return {
user_name: norm(moduleConfig.user_name?.default, osUsername),
communication_language: norm(moduleConfig.communication_language?.default, 'English'),
document_output_language: norm(moduleConfig.document_output_language?.default, 'English'),
output_folder: norm(moduleConfig.output_folder?.default, '_bmad-output'),
};
} catch (error) {
console.warn(`Failed to load module.yaml, falling back to defaults: ${error.message}`);
return {
user_name: osUsername,
communication_language: 'English',
document_output_language: 'English',
output_folder: '_bmad-output',
};
}
}
/** /**
* Collect core configuration * Collect core configuration
* @param {string} directory - Installation directory * @param {string} directory - Installation directory
@ -885,27 +933,33 @@ class UI {
(!options.userName || !options.communicationLanguage || !options.documentOutputLanguage || !options.outputFolder) (!options.userName || !options.communicationLanguage || !options.documentOutputLanguage || !options.outputFolder)
) { ) {
await configCollector.collectModuleConfig('core', directory, false, true); await configCollector.collectModuleConfig('core', directory, false, true);
} else if (options.yes) {
// Fill in defaults for any fields not provided via command-line or existing config
const isMissingOrUnresolved = (v) => v == null || (typeof v === 'string' && (v.trim() === '' || /^\{[^}]+\}$/.test(v.trim())));
const defaults = this.getDefaultCoreConfig();
for (const [key, value] of Object.entries(defaults)) {
if (isMissingOrUnresolved(configCollector.collectedConfig.core[key])) {
configCollector.collectedConfig.core[key] = value;
}
}
} }
} else if (options.yes) { } else if (options.yes) {
// Use all defaults when --yes flag is set // Use all defaults when --yes flag is set, merging with any existing config
await configCollector.loadExistingConfig(directory); await configCollector.loadExistingConfig(directory);
const existingConfig = configCollector.collectedConfig.core || {}; const existingConfig = configCollector.collectedConfig.core || {};
const defaults = this.getDefaultCoreConfig();
configCollector.collectedConfig.core = { ...defaults, ...existingConfig };
// If no existing config, use defaults // Clean up any unresolved placeholder tokens from existing config
if (Object.keys(existingConfig).length === 0) { const isMissingOrUnresolved = (v) => v == null || (typeof v === 'string' && (v.trim() === '' || /^\{[^}]+\}$/.test(v.trim())));
let safeUsername; for (const [key, value] of Object.entries(configCollector.collectedConfig.core)) {
try { if (isMissingOrUnresolved(value)) {
safeUsername = os.userInfo().username; configCollector.collectedConfig.core[key] = defaults[key];
} catch {
safeUsername = process.env.USER || process.env.USERNAME || 'User';
} }
const defaultUsername = safeUsername.charAt(0).toUpperCase() + safeUsername.slice(1); }
configCollector.collectedConfig.core = {
user_name: defaultUsername, if (Object.keys(existingConfig).length === 0) {
communication_language: 'English',
document_output_language: 'English',
output_folder: '_bmad-output',
};
await prompts.log.info('Using default configuration (--yes flag)'); await prompts.log.info('Using default configuration (--yes flag)');
} }
} else { } else {