diff --git a/tools/installer/modules/bmad-module-lib.js b/tools/installer/modules/bmad-module-lib.js index 8a22c5995..a6021fb1c 100644 --- a/tools/installer/modules/bmad-module-lib.js +++ b/tools/installer/modules/bmad-module-lib.js @@ -81,9 +81,11 @@ async function readPluginManifest(dir) { if (parsed && typeof parsed === 'object' && parsed.bmad && typeof parsed.bmad === 'object') { return parsed; } - } catch { - // Malformed JSON — treat as "not a new-system module" and let the legacy - // resolver (or validateDeclaredPaths at install time) surface the problem. + } catch (error) { + // Malformed JSON — fall back to the legacy resolver (or validateDeclaredPaths + // at install time) rather than hard-failing, but warn so the corruption is + // not swallowed silently and looks indistinguishable from a missing file. + process.stderr.write(`[bmad-module] warning: ignoring invalid JSON in ${manifestPath}: ${error.message}\n`); } return null; } diff --git a/tools/installer/ui.js b/tools/installer/ui.js index d5756c6d8..86ae02706 100644 --- a/tools/installer/ui.js +++ b/tools/installer/ui.js @@ -1231,6 +1231,12 @@ class UI { .map((s) => s.trim()) .filter(Boolean); + // Non-interactive mode: a source the user explicitly asked for must not be + // silently dropped. Collect failures and throw after attempting every source + // so the install fails (non-zero exit) instead of completing with the + // requested module missing. + const failures = []; + for (const source of sources) { const s = await prompts.spinner(); s.start(`Resolving ${source}...`); @@ -1242,6 +1248,7 @@ class UI { } catch (error) { s.error(`Failed to resolve ${source}`); await prompts.log.error(` ${error.message}`); + failures.push(`${source}: ${error.message}`); continue; } @@ -1267,6 +1274,7 @@ class UI { } catch (discoverError) { s2.error('Failed to discover modules'); await prompts.log.error(` ${discoverError.message}`); + failures.push(`${source}: ${discoverError.message}`); continue; } } else { @@ -1299,12 +1307,20 @@ class UI { const resolved = await customMgr.resolvePlugin(sourceResult.rootDir, directPlugin, sourceResult.sourceUrl, localPath); allResolved.push(...resolved); } catch (resolveError) { - await prompts.log.warn(` Could not resolve ${source}: ${resolveError.message}`); + s2.error(`Failed to resolve ${source}`); + await prompts.log.error(` ${resolveError.message}`); + failures.push(`${source}: ${resolveError.message}`); + continue; } } } s2.stop(`Found ${allResolved.length} module${allResolved.length === 1 ? '' : 's'}`); + if (allResolved.length === 0) { + failures.push(`${source}: no installable module found at this source`); + continue; + } + for (const mod of allResolved) { allCodes.push(mod.code); const versionStr = mod.version ? ` v${mod.version}` : ''; @@ -1312,6 +1328,10 @@ class UI { } } + if (failures.length > 0) { + throw new Error(`Could not resolve ${failures.length} custom source(s):\n - ${failures.join('\n - ')}`); + } + return allCodes; }