Compare commits
No commits in common. "7302f350b566f8d17393eb688edd7a591aa636ad" and "d03ba50a6058dd624cfd91d99aefd6b6317944c0" have entirely different histories.
7302f350b5
...
d03ba50a60
|
|
@ -835,15 +835,14 @@ class Manifest {
|
||||||
// Check if this is a custom module (from user-provided URL)
|
// Check if this is a custom module (from user-provided URL)
|
||||||
const { CustomModuleManager } = require('../modules/custom-module-manager');
|
const { CustomModuleManager } = require('../modules/custom-module-manager');
|
||||||
const customMgr = new CustomModuleManager();
|
const customMgr = new CustomModuleManager();
|
||||||
const resolved = customMgr.getResolution(moduleName);
|
|
||||||
const customSource = await customMgr.findModuleSourceByCode(moduleName);
|
const customSource = await customMgr.findModuleSourceByCode(moduleName);
|
||||||
if (customSource || resolved) {
|
if (customSource) {
|
||||||
const customVersion = resolved?.version || (await this._readMarketplaceVersion(moduleName, moduleSourcePath));
|
const customVersion = await this._readMarketplaceVersion(moduleName, moduleSourcePath);
|
||||||
return {
|
return {
|
||||||
version: customVersion,
|
version: customVersion,
|
||||||
source: 'custom',
|
source: 'custom',
|
||||||
npmPackage: null,
|
npmPackage: null,
|
||||||
repoUrl: resolved?.repoUrl || null,
|
repoUrl: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -104,7 +104,6 @@ class CustomModuleManager {
|
||||||
* @param {string} repoUrl - GitHub repository URL
|
* @param {string} repoUrl - GitHub repository URL
|
||||||
* @param {Object} [options] - Clone options
|
* @param {Object} [options] - Clone options
|
||||||
* @param {boolean} [options.silent] - Suppress spinner output
|
* @param {boolean} [options.silent] - Suppress spinner output
|
||||||
* @param {boolean} [options.skipInstall] - Skip npm install (for browsing before user confirms)
|
|
||||||
* @returns {string} Path to the cloned repository
|
* @returns {string} Path to the cloned repository
|
||||||
*/
|
*/
|
||||||
async cloneRepo(repoUrl, options = {}) {
|
async cloneRepo(repoUrl, options = {}) {
|
||||||
|
|
@ -160,9 +159,9 @@ class CustomModuleManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Install dependencies if package.json exists (skip during browsing/analysis)
|
// Install dependencies if package.json exists
|
||||||
const packageJsonPath = path.join(repoCacheDir, 'package.json');
|
const packageJsonPath = path.join(repoCacheDir, 'package.json');
|
||||||
if (!options.skipInstall && (await fs.pathExists(packageJsonPath))) {
|
if (await fs.pathExists(packageJsonPath)) {
|
||||||
const installSpinner = await createSpinner();
|
const installSpinner = await createSpinner();
|
||||||
installSpinner.start(`Installing dependencies for ${owner}/${repo}...`);
|
installSpinner.start(`Installing dependencies for ${owner}/${repo}...`);
|
||||||
try {
|
try {
|
||||||
|
|
@ -188,17 +187,15 @@ class CustomModuleManager {
|
||||||
* Results are cached in _resolutionCache keyed by module code.
|
* Results are cached in _resolutionCache keyed by module code.
|
||||||
* @param {string} repoPath - Absolute path to the cloned repository
|
* @param {string} repoPath - Absolute path to the cloned repository
|
||||||
* @param {Object} plugin - Raw plugin object from marketplace.json
|
* @param {Object} plugin - Raw plugin object from marketplace.json
|
||||||
* @param {string} [repoUrl] - Original GitHub URL for manifest tracking
|
|
||||||
* @returns {Promise<Array<Object>>} Array of ResolvedModule objects
|
* @returns {Promise<Array<Object>>} Array of ResolvedModule objects
|
||||||
*/
|
*/
|
||||||
async resolvePlugin(repoPath, plugin, repoUrl) {
|
async resolvePlugin(repoPath, plugin) {
|
||||||
const { PluginResolver } = require('./plugin-resolver');
|
const { PluginResolver } = require('./plugin-resolver');
|
||||||
const resolver = new PluginResolver();
|
const resolver = new PluginResolver();
|
||||||
const resolved = await resolver.resolve(repoPath, plugin);
|
const resolved = await resolver.resolve(repoPath, plugin);
|
||||||
|
|
||||||
// Stamp repo URL onto each resolved module for manifest tracking
|
// Cache each resolved module by its code for lookup during install
|
||||||
for (const mod of resolved) {
|
for (const mod of resolved) {
|
||||||
if (repoUrl) mod.repoUrl = repoUrl;
|
|
||||||
CustomModuleManager._resolutionCache.set(mod.code, mod);
|
CustomModuleManager._resolutionCache.set(mod.code, mod);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -291,8 +288,6 @@ class CustomModuleManager {
|
||||||
|
|
||||||
// Search through all custom repo caches
|
// Search through all custom repo caches
|
||||||
try {
|
try {
|
||||||
const { PluginResolver } = require('./plugin-resolver');
|
|
||||||
const resolver = new PluginResolver();
|
|
||||||
const owners = await fs.readdir(cacheDir, { withFileTypes: true });
|
const owners = await fs.readdir(cacheDir, { withFileTypes: true });
|
||||||
for (const ownerEntry of owners) {
|
for (const ownerEntry of owners) {
|
||||||
if (!ownerEntry.isDirectory()) continue;
|
if (!ownerEntry.isDirectory()) continue;
|
||||||
|
|
@ -308,37 +303,14 @@ class CustomModuleManager {
|
||||||
try {
|
try {
|
||||||
const data = JSON.parse(await fs.readFile(marketplacePath, 'utf8'));
|
const data = JSON.parse(await fs.readFile(marketplacePath, 'utf8'));
|
||||||
for (const plugin of data.plugins || []) {
|
for (const plugin of data.plugins || []) {
|
||||||
// Direct name match (legacy behavior)
|
|
||||||
if (plugin.name === moduleCode) {
|
if (plugin.name === moduleCode) {
|
||||||
|
// Found the module - find its source
|
||||||
const sourcePath = plugin.source ? path.join(repoPath, plugin.source) : repoPath;
|
const sourcePath = plugin.source ? path.join(repoPath, plugin.source) : repoPath;
|
||||||
const moduleYaml = path.join(sourcePath, 'module.yaml');
|
const moduleYaml = path.join(sourcePath, 'module.yaml');
|
||||||
if (await fs.pathExists(moduleYaml)) {
|
if (await fs.pathExists(moduleYaml)) {
|
||||||
return sourcePath;
|
return sourcePath;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolve plugin to check if any module.yaml code matches
|
|
||||||
if (plugin.skills && plugin.skills.length > 0) {
|
|
||||||
try {
|
|
||||||
const resolved = await resolver.resolve(repoPath, plugin);
|
|
||||||
for (const mod of resolved) {
|
|
||||||
if (mod.code === moduleCode) {
|
|
||||||
// Derive repo URL from cache path for manifest tracking
|
|
||||||
const repoUrl = `https://github.com/${ownerEntry.name}/${repoEntry.name}`;
|
|
||||||
mod.repoUrl = repoUrl;
|
|
||||||
CustomModuleManager._resolutionCache.set(mod.code, mod);
|
|
||||||
if (mod.moduleYamlPath) {
|
|
||||||
return path.dirname(mod.moduleYamlPath);
|
|
||||||
}
|
|
||||||
if (mod.skillPaths && mod.skillPaths.length > 0) {
|
|
||||||
return path.dirname(mod.skillPaths[0]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// Skip unresolvable plugins
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// Skip malformed marketplace.json
|
// Skip malformed marketplace.json
|
||||||
|
|
|
||||||
|
|
@ -326,20 +326,15 @@ class OfficialModules {
|
||||||
if (fileTrackingCallback) fileTrackingCallback(helpTarget);
|
if (fileTrackingCallback) fileTrackingCallback(helpTarget);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create directories declared in module.yaml (strategies 1-4 may have these)
|
|
||||||
if (!options.skipModuleInstaller) {
|
|
||||||
await this.createModuleDirectories(resolved.code, bmadDir, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update manifest
|
// Update manifest
|
||||||
const { Manifest } = require('../core/manifest');
|
const { Manifest } = require('../core/manifest');
|
||||||
const manifestObj = new Manifest();
|
const manifestObj = new Manifest();
|
||||||
|
|
||||||
await manifestObj.addModule(bmadDir, resolved.code, {
|
await manifestObj.addModule(bmadDir, resolved.code, {
|
||||||
version: resolved.version || null,
|
version: resolved.version || '',
|
||||||
source: 'custom',
|
source: `custom:${resolved.pluginName}`,
|
||||||
npmPackage: null,
|
npmPackage: '',
|
||||||
repoUrl: resolved.repoUrl || null,
|
repoUrl: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
return { success: true, module: resolved.code, path: targetPath, versionInfo: { version: resolved.version || '' } };
|
return { success: true, module: resolved.code, path: targetPath, versionInfo: { version: resolved.version || '' } };
|
||||||
|
|
|
||||||
|
|
@ -33,16 +33,11 @@ class PluginResolver {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolve skill paths to absolute, constrain to repo root, filter non-existent
|
// Resolve skill paths to absolute and filter out non-existent
|
||||||
const repoRoot = path.resolve(repoPath);
|
|
||||||
const skillPaths = [];
|
const skillPaths = [];
|
||||||
for (const rel of skillRelPaths) {
|
for (const rel of skillRelPaths) {
|
||||||
const normalized = rel.replace(/^\.\//, '');
|
const normalized = rel.replace(/^\.\//, '');
|
||||||
const abs = path.resolve(repoPath, normalized);
|
const abs = path.join(repoPath, normalized);
|
||||||
// Guard against path traversal (.. segments, absolute paths in marketplace.json)
|
|
||||||
if (!abs.startsWith(repoRoot + path.sep) && abs !== repoRoot) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (await fs.pathExists(abs)) {
|
if (await fs.pathExists(abs)) {
|
||||||
skillPaths.push(abs);
|
skillPaths.push(abs);
|
||||||
}
|
}
|
||||||
|
|
@ -389,7 +384,7 @@ class PluginResolver {
|
||||||
_escapeCSVField(value) {
|
_escapeCSVField(value) {
|
||||||
if (!value) return '';
|
if (!value) return '';
|
||||||
if (value.includes(',') || value.includes('"') || value.includes('\n')) {
|
if (value.includes(',') || value.includes('"') || value.includes('\n')) {
|
||||||
return `"${value.replaceAll('"', '""')}"`;
|
return `"${value.replace(/"/g, '""')}"`;
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -863,11 +863,11 @@ class UI {
|
||||||
'UNVERIFIED MODULE: This module has not been reviewed by the BMad team.\n' + ' Only install modules from sources you trust.',
|
'UNVERIFIED MODULE: This module has not been reviewed by the BMad team.\n' + ' Only install modules from sources you trust.',
|
||||||
);
|
);
|
||||||
|
|
||||||
// Clone the repo so we can resolve plugin structures (skip npm install until user confirms)
|
// Clone the repo so we can resolve plugin structures
|
||||||
s.start('Cloning repository...');
|
s.start('Cloning repository...');
|
||||||
let repoPath;
|
let repoPath;
|
||||||
try {
|
try {
|
||||||
repoPath = await customMgr.cloneRepo(url.trim(), { skipInstall: true });
|
repoPath = await customMgr.cloneRepo(url.trim());
|
||||||
s.stop('Repository cloned');
|
s.stop('Repository cloned');
|
||||||
} catch (cloneError) {
|
} catch (cloneError) {
|
||||||
s.error('Failed to clone repository');
|
s.error('Failed to clone repository');
|
||||||
|
|
@ -881,7 +881,7 @@ class UI {
|
||||||
const allResolved = [];
|
const allResolved = [];
|
||||||
for (const plugin of plugins) {
|
for (const plugin of plugins) {
|
||||||
try {
|
try {
|
||||||
const resolved = await customMgr.resolvePlugin(repoPath, plugin.rawPlugin, url.trim());
|
const resolved = await customMgr.resolvePlugin(repoPath, plugin.rawPlugin);
|
||||||
if (resolved.length > 0) {
|
if (resolved.length > 0) {
|
||||||
allResolved.push(...resolved);
|
allResolved.push(...resolved);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue