fix(installer): enumerate all module.yamls when walking custom-modules cache
A url-source custom-modules repo can host multiple plugins in discovery mode (e.g. skills/module-a/module.yaml and skills/module-b/module.yaml). The previous walk used searchRoot which returned only the first match, so asking for module-b would surface module-a's yaml, fail the code/name check, and skip the repo entirely — never inspecting module-b. Splits the candidate-path traversal into searchRootAll (returns every module.yaml in priority order) and a thin searchRoot wrapper for the existing single-module fallbacks. The custom-modules walk now iterates every yaml per repo and matches each against code or name.
This commit is contained in:
parent
b695d1e1bd
commit
c777989104
|
|
@ -103,11 +103,14 @@ async function resolveInstalledModuleYaml(moduleName) {
|
||||||
const builtIn = path.join(getModulePath(moduleName), 'module.yaml');
|
const builtIn = path.join(getModulePath(moduleName), 'module.yaml');
|
||||||
if (await fs.pathExists(builtIn)) return builtIn;
|
if (await fs.pathExists(builtIn)) return builtIn;
|
||||||
|
|
||||||
// Search a resolved root directory using the same candidate-path pattern.
|
// Collect every module.yaml under a root using the standard candidate paths.
|
||||||
async function searchRoot(root) {
|
// Url-source repos can host multiple plugins (discovery mode), so we need all
|
||||||
|
// matches, not just the first. Returned in priority order.
|
||||||
|
async function searchRootAll(root) {
|
||||||
|
const results = [];
|
||||||
for (const dir of ['skills', 'src']) {
|
for (const dir of ['skills', 'src']) {
|
||||||
const direct = path.join(root, dir, 'module.yaml');
|
const direct = path.join(root, dir, 'module.yaml');
|
||||||
if (await fs.pathExists(direct)) return direct;
|
if (await fs.pathExists(direct)) results.push(direct);
|
||||||
|
|
||||||
const dirPath = path.join(root, dir);
|
const dirPath = path.join(root, dir);
|
||||||
if (await fs.pathExists(dirPath)) {
|
if (await fs.pathExists(dirPath)) {
|
||||||
|
|
@ -115,7 +118,7 @@ async function resolveInstalledModuleYaml(moduleName) {
|
||||||
for (const entry of entries) {
|
for (const entry of entries) {
|
||||||
if (!entry.isDirectory()) continue;
|
if (!entry.isDirectory()) continue;
|
||||||
const nested = path.join(dirPath, entry.name, 'module.yaml');
|
const nested = path.join(dirPath, entry.name, 'module.yaml');
|
||||||
if (await fs.pathExists(nested)) return nested;
|
if (await fs.pathExists(nested)) results.push(nested);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -125,12 +128,19 @@ async function resolveInstalledModuleYaml(moduleName) {
|
||||||
for (const entry of rootEntries) {
|
for (const entry of rootEntries) {
|
||||||
if (!entry.isDirectory() || !entry.name.endsWith('-setup')) continue;
|
if (!entry.isDirectory() || !entry.name.endsWith('-setup')) continue;
|
||||||
const setupAssets = path.join(root, entry.name, 'assets', 'module.yaml');
|
const setupAssets = path.join(root, entry.name, 'assets', 'module.yaml');
|
||||||
if (await fs.pathExists(setupAssets)) return setupAssets;
|
if (await fs.pathExists(setupAssets)) results.push(setupAssets);
|
||||||
}
|
}
|
||||||
|
|
||||||
const atRoot = path.join(root, 'module.yaml');
|
const atRoot = path.join(root, 'module.yaml');
|
||||||
if (await fs.pathExists(atRoot)) return atRoot;
|
if (await fs.pathExists(atRoot)) results.push(atRoot);
|
||||||
return null;
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Backwards-compatible single-result variant for the existing external-cache
|
||||||
|
// and resolution-cache fallbacks (one module per root by construction).
|
||||||
|
async function searchRoot(root) {
|
||||||
|
const all = await searchRootAll(root);
|
||||||
|
return all.length > 0 ? all[0] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const cacheRoot = getExternalModuleCachePath(moduleName);
|
const cacheRoot = getExternalModuleCachePath(moduleName);
|
||||||
|
|
@ -155,7 +165,8 @@ async function resolveInstalledModuleYaml(moduleName) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback: url-source custom modules cloned to ~/.bmad/cache/custom-modules/.
|
// Fallback: url-source custom modules cloned to ~/.bmad/cache/custom-modules/.
|
||||||
// Walk every cached repo, locate its module.yaml via searchRoot, and match by
|
// Walk every cached repo, enumerate ALL module.yaml files via searchRootAll
|
||||||
|
// (a single repo can host multiple plugins in discovery mode), and match by
|
||||||
// the yaml's `code` or `name` field. This works on re-install runs where
|
// the yaml's `code` or `name` field. This works on re-install runs where
|
||||||
// _resolutionCache is empty and covers both discovery-mode (with marketplace.json)
|
// _resolutionCache is empty and covers both discovery-mode (with marketplace.json)
|
||||||
// and direct-mode modules, since we identify repo roots by .bmad-source.json
|
// and direct-mode modules, since we identify repo roots by .bmad-source.json
|
||||||
|
|
@ -167,15 +178,16 @@ async function resolveInstalledModuleYaml(moduleName) {
|
||||||
const customMgr = new CustomModuleManager();
|
const customMgr = new CustomModuleManager();
|
||||||
const repoRoots = await customMgr._findCacheRepoRoots(customCacheDir);
|
const repoRoots = await customMgr._findCacheRepoRoots(customCacheDir);
|
||||||
for (const { repoPath } of repoRoots) {
|
for (const { repoPath } of repoRoots) {
|
||||||
const candidate = await searchRoot(repoPath);
|
const candidates = await searchRootAll(repoPath);
|
||||||
if (!candidate) continue;
|
for (const candidate of candidates) {
|
||||||
try {
|
try {
|
||||||
const parsed = yaml.parse(await fs.readFile(candidate, 'utf8'));
|
const parsed = yaml.parse(await fs.readFile(candidate, 'utf8'));
|
||||||
if (parsed && (parsed.code === moduleName || parsed.name === moduleName)) {
|
if (parsed && (parsed.code === moduleName || parsed.name === moduleName)) {
|
||||||
return candidate;
|
return candidate;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Malformed yaml — skip
|
||||||
}
|
}
|
||||||
} catch {
|
|
||||||
// Malformed yaml — skip
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue