Fix: --custom-content flag and workflow config.yaml copying (#1651)
* fix custom install bug * fix manager.js * From PR #1624: added empty module.yaml handling (skip + warn) and removed paths from the config to match promptCustomContentSource() * fix: custom-content quick-update ENOENT, pass --custom-content through, add PR#1624 improvements to allow update installs to work using non-interactive mode
This commit is contained in:
parent
1937552da3
commit
382ab8ed45
|
|
@ -527,8 +527,13 @@ 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)) {
|
||||
|
|
@ -542,15 +547,12 @@ 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);
|
||||
|
|
@ -609,8 +611,13 @@ 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)) {
|
||||
|
|
@ -624,15 +631,12 @@ 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);
|
||||
|
|
@ -949,12 +953,11 @@ class Installer {
|
|||
if (!isCustomModule && config._customModuleSources && config._customModuleSources.has(moduleName)) {
|
||||
customInfo = config._customModuleSources.get(moduleName);
|
||||
isCustomModule = true;
|
||||
if (
|
||||
customInfo.sourcePath &&
|
||||
(customInfo.sourcePath.startsWith('_config') || customInfo.sourcePath.includes('_config/custom')) &&
|
||||
!customInfo.path
|
||||
)
|
||||
customInfo.path = customInfo.sourcePath;
|
||||
if (customInfo.sourcePath && !customInfo.path) {
|
||||
customInfo.path = path.isAbsolute(customInfo.sourcePath)
|
||||
? customInfo.sourcePath
|
||||
: path.join(bmadDir, customInfo.sourcePath);
|
||||
}
|
||||
}
|
||||
|
||||
// Finally check regular custom content
|
||||
|
|
@ -2373,15 +2376,35 @@ class Installer {
|
|||
const configuredIdes = existingInstall.ides || [];
|
||||
const projectRoot = path.dirname(bmadDir);
|
||||
|
||||
// Get custom module sources from cache
|
||||
// Get custom module sources: first from --custom-content (re-cache from source), then 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)) {
|
||||
|
|
@ -2395,8 +2418,6 @@ 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)) {
|
||||
|
|
@ -2410,7 +2431,6 @@ class Installer {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load saved IDE configurations
|
||||
const savedIdeConfigs = await this.ideConfigManager.loadAllIdeConfigs(bmadDir);
|
||||
|
|
@ -2544,6 +2564,7 @@ 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
|
||||
|
|
|
|||
|
|
@ -734,8 +734,10 @@ class ModuleManager {
|
|||
continue;
|
||||
}
|
||||
|
||||
// Skip config.yaml templates - we'll generate clean ones with actual values
|
||||
if (file === 'config.yaml' || file.endsWith('/config.yaml')) {
|
||||
// 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') {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -245,11 +245,48 @@ class UI {
|
|||
|
||||
// Handle quick update separately
|
||||
if (actionType === 'quick-update') {
|
||||
// Quick update doesn't install custom content - just updates existing modules
|
||||
// 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,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
actionType: 'quick-update',
|
||||
directory: confirmedDirectory,
|
||||
customContent: { hasCustomContent: false },
|
||||
customContent: customContentForQuickUpdate,
|
||||
skipPrompts: options.yes || false,
|
||||
};
|
||||
}
|
||||
|
|
@ -305,6 +342,7 @@ 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);
|
||||
|
|
@ -326,6 +364,11 @@ 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;
|
||||
|
|
@ -333,6 +376,11 @@ 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) {
|
||||
|
|
@ -340,7 +388,9 @@ class UI {
|
|||
selectedCustomModules: selectedModuleIds,
|
||||
customContentConfig: {
|
||||
hasCustomContent: true,
|
||||
paths: customPaths,
|
||||
selected: true,
|
||||
sources,
|
||||
selectedFiles: customPaths.map((p) => path.join(p, 'module.yaml')),
|
||||
selectedModuleIds: selectedModuleIds,
|
||||
},
|
||||
};
|
||||
|
|
@ -446,6 +496,7 @@ 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);
|
||||
|
|
@ -467,6 +518,11 @@ 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;
|
||||
|
|
@ -474,12 +530,19 @@ 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,
|
||||
paths: customPaths,
|
||||
selected: true,
|
||||
sources,
|
||||
selectedFiles: customPaths.map((p) => path.join(p, 'module.yaml')),
|
||||
selectedModuleIds: selectedModuleIds,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue