fix(bmad-module): surface malformed plugin.json + fail CI custom-source resolve
- bmad-module-lib: keep the legacy-fallback null return for malformed .claude-plugin/plugin.json, but warn so corruption is no longer indistinguishable from a missing file. - ui: in the non-interactive custom-source path, collect resolution failures and throw after attempting every source (and when a source yields no module), so a CI/--custom-source install fails instead of silently omitting the module. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
c845e78aab
commit
31d115290a
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue