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:
Curtis Ide 2026-02-15 09:19:29 -07:00
parent 589f65a654
commit caff2fb815
No known key found for this signature in database
2 changed files with 127 additions and 69 deletions

View File

@ -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

View File

@ -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,
};
}