Compare commits

..

1 Commits

Author SHA1 Message Date
Wendy Smoak f370e796de
Merge f0ad64efc4 into 2d134314c9 2026-02-15 16:56:11 +00:00
4 changed files with 83 additions and 175 deletions

View File

@ -302,7 +302,6 @@ class ConfigCollector {
const configSpinner = await prompts.spinner();
configSpinner.start('Configuring modules...');
try {
for (const moduleName of defaultModules) {
const displayName = displayNameMap.get(moduleName) || moduleName.toUpperCase();
configSpinner.message(`Configuring ${displayName}...`);
@ -313,19 +312,13 @@ class ConfigCollector {
this._silentConfig = false;
}
}
} finally {
configSpinner.stop(customizeModules.length > 0 ? 'Module defaults applied' : 'Module configuration complete');
}
configSpinner.stop('Module configuration complete');
}
// Run customized modules individually (may show interactive prompts)
for (const moduleName of customizeModules) {
await this.collectModuleConfig(moduleName, projectDir);
}
if (customizeModules.length > 0) {
await prompts.log.step('Module configuration complete');
}
}
// Add metadata
@ -1246,6 +1239,7 @@ class ConfigCollector {
hasOutput = true;
const message = valueMessages[selectedValue];
await prompts.log.message('');
for (const line of message.trim().split('\n')) {
const trimmedLine = line.trim();
if (trimmedLine.endsWith(':') && !trimmedLine.startsWith(' ')) {

View File

@ -527,13 +527,8 @@ class Installer {
const cachedModules = await fs.readdir(cacheDir, { withFileTypes: true });
for (const cachedModule of cachedModules) {
if (cachedModule.isDirectory()) {
const moduleId = cachedModule.name;
const cachedPath = path.join(cacheDir, moduleId);
// Skip if path doesn't exist (broken symlink, deleted dir) - avoids lstat ENOENT
if (!(await fs.pathExists(cachedPath)) || !cachedModule.isDirectory()) {
continue;
}
// Skip if we already have this module from manifest
if (customModulePaths.has(moduleId)) {
@ -547,12 +542,15 @@ class Installer {
continue;
}
const cachedPath = path.join(cacheDir, moduleId);
// Check if this is actually a custom module (has module.yaml)
const moduleYamlPath = path.join(cachedPath, 'module.yaml');
if (await fs.pathExists(moduleYamlPath)) {
customModulePaths.set(moduleId, cachedPath);
}
}
}
// Update module manager with the new custom module paths from cache
this.moduleManager.setCustomModulePaths(customModulePaths);
@ -611,13 +609,8 @@ class Installer {
const cachedModules = await fs.readdir(cacheDir, { withFileTypes: true });
for (const cachedModule of cachedModules) {
if (cachedModule.isDirectory()) {
const moduleId = cachedModule.name;
const cachedPath = path.join(cacheDir, moduleId);
// Skip if path doesn't exist (broken symlink, deleted dir) - avoids lstat ENOENT
if (!(await fs.pathExists(cachedPath)) || !cachedModule.isDirectory()) {
continue;
}
// Skip if we already have this module from manifest
if (customModulePaths.has(moduleId)) {
@ -631,12 +624,15 @@ class Installer {
continue;
}
const cachedPath = path.join(cacheDir, moduleId);
// Check if this is actually a custom module (has module.yaml)
const moduleYamlPath = path.join(cachedPath, 'module.yaml');
if (await fs.pathExists(moduleYamlPath)) {
customModulePaths.set(moduleId, cachedPath);
}
}
}
// Update module manager with the new custom module paths from cache
this.moduleManager.setCustomModulePaths(customModulePaths);
@ -953,11 +949,12 @@ class Installer {
if (!isCustomModule && config._customModuleSources && config._customModuleSources.has(moduleName)) {
customInfo = config._customModuleSources.get(moduleName);
isCustomModule = true;
if (customInfo.sourcePath && !customInfo.path) {
customInfo.path = path.isAbsolute(customInfo.sourcePath)
? customInfo.sourcePath
: path.join(bmadDir, customInfo.sourcePath);
}
if (
customInfo.sourcePath &&
(customInfo.sourcePath.startsWith('_config') || customInfo.sourcePath.includes('_config/custom')) &&
!customInfo.path
)
customInfo.path = customInfo.sourcePath;
}
// Finally check regular custom content
@ -2376,35 +2373,15 @@ class Installer {
const configuredIdes = existingInstall.ides || [];
const projectRoot = path.dirname(bmadDir);
// Get custom module sources: first from --custom-content (re-cache from source), then from cache
// Get custom module sources from cache
const customModuleSources = new Map();
if (config.customContent?.sources?.length > 0) {
for (const source of config.customContent.sources) {
if (source.id && source.path && (await fs.pathExists(source.path))) {
customModuleSources.set(source.id, {
id: source.id,
name: source.name || source.id,
sourcePath: source.path,
cached: false, // From CLI, will be re-cached
});
}
}
}
const cacheDir = path.join(bmadDir, '_config', 'custom');
if (await fs.pathExists(cacheDir)) {
const cachedModules = await fs.readdir(cacheDir, { withFileTypes: true });
for (const cachedModule of cachedModules) {
if (cachedModule.isDirectory()) {
const moduleId = cachedModule.name;
const cachedPath = path.join(cacheDir, moduleId);
// Skip if path doesn't exist (broken symlink, deleted dir) - avoids lstat ENOENT
if (!(await fs.pathExists(cachedPath))) {
continue;
}
if (!cachedModule.isDirectory()) {
continue;
}
// Skip if we already have this module from manifest
if (customModuleSources.has(moduleId)) {
@ -2418,6 +2395,8 @@ class Installer {
continue;
}
const cachedPath = path.join(cacheDir, moduleId);
// Check if this is actually a custom module (has module.yaml)
const moduleYamlPath = path.join(cachedPath, 'module.yaml');
if (await fs.pathExists(moduleYamlPath)) {
@ -2431,6 +2410,7 @@ class Installer {
}
}
}
}
// Load saved IDE configurations
const savedIdeConfigs = await this.ideConfigManager.loadAllIdeConfigs(bmadDir);
@ -2564,7 +2544,6 @@ class Installer {
_savedIdeConfigs: savedIdeConfigs, // Pass saved IDE configs to installer
_customModuleSources: customModuleSources, // Pass custom module sources for updates
_existingModules: installedModules, // Pass all installed modules for manifest generation
customContent: config.customContent, // Pass through for re-caching from source
};
// Call the standard install method

View File

@ -734,10 +734,8 @@ class ModuleManager {
continue;
}
// Skip module root config.yaml only - generated by config collector with actual values
// Workflow-level config.yaml (e.g. workflows/orchestrate-story/config.yaml) must be copied
// for custom modules that use workflow-specific configuration
if (file === 'config.yaml') {
// Skip config.yaml templates - we'll generate clean ones with actual values
if (file === 'config.yaml' || file.endsWith('/config.yaml')) {
continue;
}

View File

@ -245,48 +245,11 @@ class UI {
// Handle quick update separately
if (actionType === 'quick-update') {
// Pass --custom-content through so installer can re-cache if cache is missing
let customContentForQuickUpdate = { hasCustomContent: false };
if (options.customContent) {
const paths = options.customContent
.split(',')
.map((p) => p.trim())
.filter(Boolean);
if (paths.length > 0) {
const customPaths = [];
const selectedModuleIds = [];
const sources = [];
for (const customPath of paths) {
const expandedPath = this.expandUserPath(customPath);
const validation = this.validateCustomContentPathSync(expandedPath);
if (validation) continue;
let moduleMeta;
try {
const moduleYamlPath = path.join(expandedPath, 'module.yaml');
moduleMeta = require('yaml').parse(await fs.readFile(moduleYamlPath, 'utf-8'));
} catch {
continue;
}
if (!moduleMeta?.code) continue;
customPaths.push(expandedPath);
selectedModuleIds.push(moduleMeta.code);
sources.push({ path: expandedPath, id: moduleMeta.code, name: moduleMeta.name || moduleMeta.code });
}
if (customPaths.length > 0) {
customContentForQuickUpdate = {
hasCustomContent: true,
selected: true,
sources,
selectedFiles: customPaths.map((p) => path.join(p, 'module.yaml')),
selectedModuleIds,
};
}
}
}
// Quick update doesn't install custom content - just updates existing modules
return {
actionType: 'quick-update',
directory: confirmedDirectory,
customContent: customContentForQuickUpdate,
customContent: { hasCustomContent: false },
skipPrompts: options.yes || false,
};
}
@ -342,7 +305,6 @@ class UI {
// Build custom content config similar to promptCustomContentSource
const customPaths = [];
const selectedModuleIds = [];
const sources = [];
for (const customPath of paths) {
const expandedPath = this.expandUserPath(customPath);
@ -364,11 +326,6 @@ class UI {
continue;
}
if (!moduleMeta) {
await prompts.log.warn(`Skipping custom content path: ${customPath} - module.yaml is empty`);
continue;
}
if (!moduleMeta.code) {
await prompts.log.warn(`Skipping custom content path: ${customPath} - module.yaml missing 'code' field`);
continue;
@ -376,11 +333,6 @@ class UI {
customPaths.push(expandedPath);
selectedModuleIds.push(moduleMeta.code);
sources.push({
path: expandedPath,
id: moduleMeta.code,
name: moduleMeta.name || moduleMeta.code,
});
}
if (customPaths.length > 0) {
@ -388,9 +340,7 @@ class UI {
selectedCustomModules: selectedModuleIds,
customContentConfig: {
hasCustomContent: true,
selected: true,
sources,
selectedFiles: customPaths.map((p) => path.join(p, 'module.yaml')),
paths: customPaths,
selectedModuleIds: selectedModuleIds,
},
};
@ -496,7 +446,6 @@ class UI {
// Build custom content config similar to promptCustomContentSource
const customPaths = [];
const selectedModuleIds = [];
const sources = [];
for (const customPath of paths) {
const expandedPath = this.expandUserPath(customPath);
@ -518,11 +467,6 @@ class UI {
continue;
}
if (!moduleMeta) {
await prompts.log.warn(`Skipping custom content path: ${customPath} - module.yaml is empty`);
continue;
}
if (!moduleMeta.code) {
await prompts.log.warn(`Skipping custom content path: ${customPath} - module.yaml missing 'code' field`);
continue;
@ -530,19 +474,12 @@ class UI {
customPaths.push(expandedPath);
selectedModuleIds.push(moduleMeta.code);
sources.push({
path: expandedPath,
id: moduleMeta.code,
name: moduleMeta.name || moduleMeta.code,
});
}
if (customPaths.length > 0) {
customContentConfig = {
hasCustomContent: true,
selected: true,
sources,
selectedFiles: customPaths.map((p) => path.join(p, 'module.yaml')),
paths: customPaths,
selectedModuleIds: selectedModuleIds,
};
}