refactor(installer): move external module ops into ExternalModuleManager
Move cloneExternalModule, findExternalModuleSource, and getExternalCacheDir from ModuleManager into ExternalModuleManager where they belong. Replace this.moduleManager.isExternalModule() calls with direct ExternalModuleManager.hasModule(). Remove externalModuleManager instance from ModuleManager constructor.
This commit is contained in:
parent
89812ec846
commit
ad2833caf6
|
|
@ -16,6 +16,15 @@ const prompts = require('../../../lib/prompts');
|
||||||
const { BMAD_FOLDER_NAME } = require('../ide/shared/path-utils');
|
const { BMAD_FOLDER_NAME } = require('../ide/shared/path-utils');
|
||||||
const { InstallPaths } = require('./install-paths');
|
const { InstallPaths } = require('./install-paths');
|
||||||
|
|
||||||
|
let _externalManager;
|
||||||
|
function getExternalManager() {
|
||||||
|
if (!_externalManager) {
|
||||||
|
const { ExternalModuleManager } = require('../modules/external-manager');
|
||||||
|
_externalManager = new ExternalModuleManager();
|
||||||
|
}
|
||||||
|
return _externalManager;
|
||||||
|
}
|
||||||
|
|
||||||
class Installer {
|
class Installer {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.detector = new Detector();
|
this.detector = new Detector();
|
||||||
|
|
@ -204,7 +213,7 @@ class Installer {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if this is an external official module - skip cache for those
|
// Check if this is an external official module - skip cache for those
|
||||||
const isExternal = await this.moduleManager.isExternalModule(moduleId);
|
const isExternal = await getExternalManager().hasModule(moduleId);
|
||||||
if (isExternal) {
|
if (isExternal) {
|
||||||
// External modules are handled via cloneExternalModule, not from cache
|
// External modules are handled via cloneExternalModule, not from cache
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -288,7 +297,7 @@ class Installer {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if this is an external official module - skip cache for those
|
// Check if this is an external official module - skip cache for those
|
||||||
const isExternal = await this.moduleManager.isExternalModule(moduleId);
|
const isExternal = await getExternalManager().hasModule(moduleId);
|
||||||
if (isExternal) {
|
if (isExternal) {
|
||||||
// External modules are handled via cloneExternalModule, not from cache
|
// External modules are handled via cloneExternalModule, not from cache
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -1834,7 +1843,7 @@ class Installer {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if this is an external official module - skip cache for those
|
// Check if this is an external official module - skip cache for those
|
||||||
const isExternal = await this.moduleManager.isExternalModule(moduleId);
|
const isExternal = await getExternalManager().hasModule(moduleId);
|
||||||
if (isExternal) {
|
if (isExternal) {
|
||||||
// External modules are handled via cloneExternalModule, not from cache
|
// External modules are handled via cloneExternalModule, not from cache
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -2060,7 +2069,7 @@ class Installer {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if this is an external official module - skip cache for those
|
// Check if this is an external official module - skip cache for those
|
||||||
const isExternal = await this.moduleManager.isExternalModule(moduleId);
|
const isExternal = await getExternalManager().hasModule(moduleId);
|
||||||
if (isExternal) {
|
if (isExternal) {
|
||||||
// External modules are handled via cloneExternalModule, not from cache
|
// External modules are handled via cloneExternalModule, not from cache
|
||||||
continue;
|
continue;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
|
const os = require('node:os');
|
||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
|
const { execSync } = require('node:child_process');
|
||||||
const yaml = require('yaml');
|
const yaml = require('yaml');
|
||||||
const prompts = require('../../../lib/prompts');
|
const prompts = require('../../../lib/prompts');
|
||||||
|
|
||||||
|
|
@ -131,6 +133,191 @@ class ExternalModuleManager {
|
||||||
const module = await this.getModuleByCode(code);
|
const module = await this.getModuleByCode(code);
|
||||||
return module ? module.moduleDefinition : null;
|
return module ? module.moduleDefinition : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the cache directory for external modules
|
||||||
|
* @returns {string} Path to the external modules cache directory
|
||||||
|
*/
|
||||||
|
getExternalCacheDir() {
|
||||||
|
const cacheDir = path.join(os.homedir(), '.bmad', 'cache', 'external-modules');
|
||||||
|
return cacheDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clone an external module repository to cache
|
||||||
|
* @param {string} moduleCode - Code of the external module
|
||||||
|
* @param {Object} options - Clone options
|
||||||
|
* @param {boolean} options.silent - Suppress spinner output
|
||||||
|
* @returns {string} Path to the cloned repository
|
||||||
|
*/
|
||||||
|
async cloneExternalModule(moduleCode, options = {}) {
|
||||||
|
const moduleInfo = await this.getModuleByCode(moduleCode);
|
||||||
|
|
||||||
|
if (!moduleInfo) {
|
||||||
|
throw new Error(`External module '${moduleCode}' not found in external-official-modules.yaml`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const cacheDir = this.getExternalCacheDir();
|
||||||
|
const moduleCacheDir = path.join(cacheDir, moduleCode);
|
||||||
|
const silent = options.silent || false;
|
||||||
|
|
||||||
|
// Create cache directory if it doesn't exist
|
||||||
|
await fs.ensureDir(cacheDir);
|
||||||
|
|
||||||
|
// Helper to create a spinner or a no-op when silent
|
||||||
|
const createSpinner = async () => {
|
||||||
|
if (silent) {
|
||||||
|
return {
|
||||||
|
start() {},
|
||||||
|
stop() {},
|
||||||
|
error() {},
|
||||||
|
message() {},
|
||||||
|
cancel() {},
|
||||||
|
clear() {},
|
||||||
|
get isSpinning() {
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
get isCancelled() {
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return await prompts.spinner();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Track if we need to install dependencies
|
||||||
|
let needsDependencyInstall = false;
|
||||||
|
let wasNewClone = false;
|
||||||
|
|
||||||
|
// Check if already cloned
|
||||||
|
if (await fs.pathExists(moduleCacheDir)) {
|
||||||
|
// Try to update if it's a git repo
|
||||||
|
const fetchSpinner = await createSpinner();
|
||||||
|
fetchSpinner.start(`Fetching ${moduleInfo.name}...`);
|
||||||
|
try {
|
||||||
|
const currentRef = execSync('git rev-parse HEAD', { cwd: moduleCacheDir, stdio: 'pipe' }).toString().trim();
|
||||||
|
// Fetch and reset to remote - works better with shallow clones than pull
|
||||||
|
execSync('git fetch origin --depth 1', {
|
||||||
|
cwd: moduleCacheDir,
|
||||||
|
stdio: ['ignore', 'pipe', 'pipe'],
|
||||||
|
env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
|
||||||
|
});
|
||||||
|
execSync('git reset --hard origin/HEAD', {
|
||||||
|
cwd: moduleCacheDir,
|
||||||
|
stdio: ['ignore', 'pipe', 'pipe'],
|
||||||
|
env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
|
||||||
|
});
|
||||||
|
const newRef = execSync('git rev-parse HEAD', { cwd: moduleCacheDir, stdio: 'pipe' }).toString().trim();
|
||||||
|
|
||||||
|
fetchSpinner.stop(`Fetched ${moduleInfo.name}`);
|
||||||
|
// Force dependency install if we got new code
|
||||||
|
if (currentRef !== newRef) {
|
||||||
|
needsDependencyInstall = true;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
fetchSpinner.error(`Fetch failed, re-downloading ${moduleInfo.name}`);
|
||||||
|
// If update fails, remove and re-clone
|
||||||
|
await fs.remove(moduleCacheDir);
|
||||||
|
wasNewClone = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
wasNewClone = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clone if not exists or was removed
|
||||||
|
if (wasNewClone) {
|
||||||
|
const fetchSpinner = await createSpinner();
|
||||||
|
fetchSpinner.start(`Fetching ${moduleInfo.name}...`);
|
||||||
|
try {
|
||||||
|
execSync(`git clone --depth 1 "${moduleInfo.url}" "${moduleCacheDir}"`, {
|
||||||
|
stdio: ['ignore', 'pipe', 'pipe'],
|
||||||
|
env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
|
||||||
|
});
|
||||||
|
fetchSpinner.stop(`Fetched ${moduleInfo.name}`);
|
||||||
|
} catch (error) {
|
||||||
|
fetchSpinner.error(`Failed to fetch ${moduleInfo.name}`);
|
||||||
|
throw new Error(`Failed to clone external module '${moduleCode}': ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Install dependencies if package.json exists
|
||||||
|
const packageJsonPath = path.join(moduleCacheDir, 'package.json');
|
||||||
|
const nodeModulesPath = path.join(moduleCacheDir, 'node_modules');
|
||||||
|
if (await fs.pathExists(packageJsonPath)) {
|
||||||
|
// Install if node_modules doesn't exist, or if package.json is newer (dependencies changed)
|
||||||
|
const nodeModulesMissing = !(await fs.pathExists(nodeModulesPath));
|
||||||
|
|
||||||
|
// Force install if we updated or cloned new
|
||||||
|
if (needsDependencyInstall || wasNewClone || nodeModulesMissing) {
|
||||||
|
const installSpinner = await createSpinner();
|
||||||
|
installSpinner.start(`Installing dependencies for ${moduleInfo.name}...`);
|
||||||
|
try {
|
||||||
|
execSync('npm install --omit=dev --no-audit --no-fund --no-progress --legacy-peer-deps', {
|
||||||
|
cwd: moduleCacheDir,
|
||||||
|
stdio: ['ignore', 'pipe', 'pipe'],
|
||||||
|
timeout: 120_000, // 2 minute timeout
|
||||||
|
});
|
||||||
|
installSpinner.stop(`Installed dependencies for ${moduleInfo.name}`);
|
||||||
|
} catch (error) {
|
||||||
|
installSpinner.error(`Failed to install dependencies for ${moduleInfo.name}`);
|
||||||
|
if (!silent) await prompts.log.warn(` ${error.message}`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Check if package.json is newer than node_modules
|
||||||
|
let packageJsonNewer = false;
|
||||||
|
try {
|
||||||
|
const packageStats = await fs.stat(packageJsonPath);
|
||||||
|
const nodeModulesStats = await fs.stat(nodeModulesPath);
|
||||||
|
packageJsonNewer = packageStats.mtime > nodeModulesStats.mtime;
|
||||||
|
} catch {
|
||||||
|
// If stat fails, assume we need to install
|
||||||
|
packageJsonNewer = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (packageJsonNewer) {
|
||||||
|
const installSpinner = await createSpinner();
|
||||||
|
installSpinner.start(`Installing dependencies for ${moduleInfo.name}...`);
|
||||||
|
try {
|
||||||
|
execSync('npm install --omit=dev --no-audit --no-fund --no-progress --legacy-peer-deps', {
|
||||||
|
cwd: moduleCacheDir,
|
||||||
|
stdio: ['ignore', 'pipe', 'pipe'],
|
||||||
|
timeout: 120_000, // 2 minute timeout
|
||||||
|
});
|
||||||
|
installSpinner.stop(`Installed dependencies for ${moduleInfo.name}`);
|
||||||
|
} catch (error) {
|
||||||
|
installSpinner.error(`Failed to install dependencies for ${moduleInfo.name}`);
|
||||||
|
if (!silent) await prompts.log.warn(` ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return moduleCacheDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the source path for an external module
|
||||||
|
* @param {string} moduleCode - Code of the external module
|
||||||
|
* @param {Object} options - Options passed to cloneExternalModule
|
||||||
|
* @returns {string|null} Path to the module source or null if not found
|
||||||
|
*/
|
||||||
|
async findExternalModuleSource(moduleCode, options = {}) {
|
||||||
|
const moduleInfo = await this.getModuleByCode(moduleCode);
|
||||||
|
|
||||||
|
if (!moduleInfo) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clone the external module repo
|
||||||
|
const cloneDir = await this.cloneExternalModule(moduleCode, options);
|
||||||
|
|
||||||
|
// The module-definition specifies the path to module.yaml relative to repo root
|
||||||
|
// We need to return the directory containing module.yaml
|
||||||
|
const moduleDefinitionPath = moduleInfo.moduleDefinition; // e.g., 'src/module.yaml'
|
||||||
|
const moduleDir = path.dirname(path.join(cloneDir, moduleDefinitionPath));
|
||||||
|
|
||||||
|
return moduleDir;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { ExternalModuleManager };
|
module.exports = { ExternalModuleManager };
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,15 @@ const fs = require('fs-extra');
|
||||||
const yaml = require('yaml');
|
const yaml = require('yaml');
|
||||||
const prompts = require('../../../lib/prompts');
|
const prompts = require('../../../lib/prompts');
|
||||||
const { getProjectRoot, getSourcePath, getModulePath } = require('../../../lib/project-root');
|
const { getProjectRoot, getSourcePath, getModulePath } = require('../../../lib/project-root');
|
||||||
const { ExternalModuleManager } = require('./external-manager');
|
|
||||||
|
let _externalManager;
|
||||||
|
function getExternalManager() {
|
||||||
|
if (!_externalManager) {
|
||||||
|
const { ExternalModuleManager } = require('./external-manager');
|
||||||
|
_externalManager = new ExternalModuleManager();
|
||||||
|
}
|
||||||
|
return _externalManager;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages the installation, updating, and removal of BMAD modules.
|
* Manages the installation, updating, and removal of BMAD modules.
|
||||||
|
|
@ -22,7 +30,6 @@ const { ExternalModuleManager } = require('./external-manager');
|
||||||
class ModuleManager {
|
class ModuleManager {
|
||||||
constructor(options = {}) {
|
constructor(options = {}) {
|
||||||
this.customModulePaths = new Map();
|
this.customModulePaths = new Map();
|
||||||
this.externalModuleManager = new ExternalModuleManager();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -171,7 +178,7 @@ class ModuleManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check external official modules
|
// Check external official modules
|
||||||
const externalSource = await this.findExternalModuleSource(moduleCode, options);
|
const externalSource = await getExternalManager().findExternalModuleSource(moduleCode, options);
|
||||||
if (externalSource) {
|
if (externalSource) {
|
||||||
return externalSource;
|
return externalSource;
|
||||||
}
|
}
|
||||||
|
|
@ -179,199 +186,6 @@ class ModuleManager {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if a module is an external official module
|
|
||||||
* @param {string} moduleCode - Code of the module to check
|
|
||||||
* @returns {boolean} True if the module is external
|
|
||||||
*/
|
|
||||||
async isExternalModule(moduleCode) {
|
|
||||||
return await this.externalModuleManager.hasModule(moduleCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the cache directory for external modules
|
|
||||||
* @returns {string} Path to the external modules cache directory
|
|
||||||
*/
|
|
||||||
getExternalCacheDir() {
|
|
||||||
const os = require('node:os');
|
|
||||||
const cacheDir = path.join(os.homedir(), '.bmad', 'cache', 'external-modules');
|
|
||||||
return cacheDir;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clone an external module repository to cache
|
|
||||||
* @param {string} moduleCode - Code of the external module
|
|
||||||
* @returns {string} Path to the cloned repository
|
|
||||||
*/
|
|
||||||
async cloneExternalModule(moduleCode, options = {}) {
|
|
||||||
const { execSync } = require('node:child_process');
|
|
||||||
const moduleInfo = await this.externalModuleManager.getModuleByCode(moduleCode);
|
|
||||||
|
|
||||||
if (!moduleInfo) {
|
|
||||||
throw new Error(`External module '${moduleCode}' not found in external-official-modules.yaml`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const cacheDir = this.getExternalCacheDir();
|
|
||||||
const moduleCacheDir = path.join(cacheDir, moduleCode);
|
|
||||||
const silent = options.silent || false;
|
|
||||||
|
|
||||||
// Create cache directory if it doesn't exist
|
|
||||||
await fs.ensureDir(cacheDir);
|
|
||||||
|
|
||||||
// Helper to create a spinner or a no-op when silent
|
|
||||||
const createSpinner = async () => {
|
|
||||||
if (silent) {
|
|
||||||
return {
|
|
||||||
start() {},
|
|
||||||
stop() {},
|
|
||||||
error() {},
|
|
||||||
message() {},
|
|
||||||
cancel() {},
|
|
||||||
clear() {},
|
|
||||||
get isSpinning() {
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
get isCancelled() {
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return await prompts.spinner();
|
|
||||||
};
|
|
||||||
|
|
||||||
// Track if we need to install dependencies
|
|
||||||
let needsDependencyInstall = false;
|
|
||||||
let wasNewClone = false;
|
|
||||||
|
|
||||||
// Check if already cloned
|
|
||||||
if (await fs.pathExists(moduleCacheDir)) {
|
|
||||||
// Try to update if it's a git repo
|
|
||||||
const fetchSpinner = await createSpinner();
|
|
||||||
fetchSpinner.start(`Fetching ${moduleInfo.name}...`);
|
|
||||||
try {
|
|
||||||
const currentRef = execSync('git rev-parse HEAD', { cwd: moduleCacheDir, stdio: 'pipe' }).toString().trim();
|
|
||||||
// Fetch and reset to remote - works better with shallow clones than pull
|
|
||||||
execSync('git fetch origin --depth 1', {
|
|
||||||
cwd: moduleCacheDir,
|
|
||||||
stdio: ['ignore', 'pipe', 'pipe'],
|
|
||||||
env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
|
|
||||||
});
|
|
||||||
execSync('git reset --hard origin/HEAD', {
|
|
||||||
cwd: moduleCacheDir,
|
|
||||||
stdio: ['ignore', 'pipe', 'pipe'],
|
|
||||||
env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
|
|
||||||
});
|
|
||||||
const newRef = execSync('git rev-parse HEAD', { cwd: moduleCacheDir, stdio: 'pipe' }).toString().trim();
|
|
||||||
|
|
||||||
fetchSpinner.stop(`Fetched ${moduleInfo.name}`);
|
|
||||||
// Force dependency install if we got new code
|
|
||||||
if (currentRef !== newRef) {
|
|
||||||
needsDependencyInstall = true;
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
fetchSpinner.error(`Fetch failed, re-downloading ${moduleInfo.name}`);
|
|
||||||
// If update fails, remove and re-clone
|
|
||||||
await fs.remove(moduleCacheDir);
|
|
||||||
wasNewClone = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
wasNewClone = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clone if not exists or was removed
|
|
||||||
if (wasNewClone) {
|
|
||||||
const fetchSpinner = await createSpinner();
|
|
||||||
fetchSpinner.start(`Fetching ${moduleInfo.name}...`);
|
|
||||||
try {
|
|
||||||
execSync(`git clone --depth 1 "${moduleInfo.url}" "${moduleCacheDir}"`, {
|
|
||||||
stdio: ['ignore', 'pipe', 'pipe'],
|
|
||||||
env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
|
|
||||||
});
|
|
||||||
fetchSpinner.stop(`Fetched ${moduleInfo.name}`);
|
|
||||||
} catch (error) {
|
|
||||||
fetchSpinner.error(`Failed to fetch ${moduleInfo.name}`);
|
|
||||||
throw new Error(`Failed to clone external module '${moduleCode}': ${error.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Install dependencies if package.json exists
|
|
||||||
const packageJsonPath = path.join(moduleCacheDir, 'package.json');
|
|
||||||
const nodeModulesPath = path.join(moduleCacheDir, 'node_modules');
|
|
||||||
if (await fs.pathExists(packageJsonPath)) {
|
|
||||||
// Install if node_modules doesn't exist, or if package.json is newer (dependencies changed)
|
|
||||||
const nodeModulesMissing = !(await fs.pathExists(nodeModulesPath));
|
|
||||||
|
|
||||||
// Force install if we updated or cloned new
|
|
||||||
if (needsDependencyInstall || wasNewClone || nodeModulesMissing) {
|
|
||||||
const installSpinner = await createSpinner();
|
|
||||||
installSpinner.start(`Installing dependencies for ${moduleInfo.name}...`);
|
|
||||||
try {
|
|
||||||
execSync('npm install --omit=dev --no-audit --no-fund --no-progress --legacy-peer-deps', {
|
|
||||||
cwd: moduleCacheDir,
|
|
||||||
stdio: ['ignore', 'pipe', 'pipe'],
|
|
||||||
timeout: 120_000, // 2 minute timeout
|
|
||||||
});
|
|
||||||
installSpinner.stop(`Installed dependencies for ${moduleInfo.name}`);
|
|
||||||
} catch (error) {
|
|
||||||
installSpinner.error(`Failed to install dependencies for ${moduleInfo.name}`);
|
|
||||||
if (!silent) await prompts.log.warn(` ${error.message}`);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Check if package.json is newer than node_modules
|
|
||||||
let packageJsonNewer = false;
|
|
||||||
try {
|
|
||||||
const packageStats = await fs.stat(packageJsonPath);
|
|
||||||
const nodeModulesStats = await fs.stat(nodeModulesPath);
|
|
||||||
packageJsonNewer = packageStats.mtime > nodeModulesStats.mtime;
|
|
||||||
} catch {
|
|
||||||
// If stat fails, assume we need to install
|
|
||||||
packageJsonNewer = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (packageJsonNewer) {
|
|
||||||
const installSpinner = await createSpinner();
|
|
||||||
installSpinner.start(`Installing dependencies for ${moduleInfo.name}...`);
|
|
||||||
try {
|
|
||||||
execSync('npm install --omit=dev --no-audit --no-fund --no-progress --legacy-peer-deps', {
|
|
||||||
cwd: moduleCacheDir,
|
|
||||||
stdio: ['ignore', 'pipe', 'pipe'],
|
|
||||||
timeout: 120_000, // 2 minute timeout
|
|
||||||
});
|
|
||||||
installSpinner.stop(`Installed dependencies for ${moduleInfo.name}`);
|
|
||||||
} catch (error) {
|
|
||||||
installSpinner.error(`Failed to install dependencies for ${moduleInfo.name}`);
|
|
||||||
if (!silent) await prompts.log.warn(` ${error.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return moduleCacheDir;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find the source path for an external module
|
|
||||||
* @param {string} moduleCode - Code of the external module
|
|
||||||
* @returns {string|null} Path to the module source or null if not found
|
|
||||||
*/
|
|
||||||
async findExternalModuleSource(moduleCode, options = {}) {
|
|
||||||
const moduleInfo = await this.externalModuleManager.getModuleByCode(moduleCode);
|
|
||||||
|
|
||||||
if (!moduleInfo) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clone the external module repo
|
|
||||||
const cloneDir = await this.cloneExternalModule(moduleCode, options);
|
|
||||||
|
|
||||||
// The module-definition specifies the path to module.yaml relative to repo root
|
|
||||||
// We need to return the directory containing module.yaml
|
|
||||||
const moduleDefinitionPath = moduleInfo.moduleDefinition; // e.g., 'src/module.yaml'
|
|
||||||
const moduleDir = path.dirname(path.join(cloneDir, moduleDefinitionPath));
|
|
||||||
|
|
||||||
return moduleDir;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Install a module
|
* Install a module
|
||||||
* @param {string} moduleName - Code of the module to install (from module.yaml)
|
* @param {string} moduleName - Code of the module to install (from module.yaml)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue