fix: support ESM and .cjs module installers in ModuleManager

Module installer loading now handles three cases:
- .cjs files loaded via require() (always CommonJS regardless of package type)
- .js files loaded via dynamic import() (works for both CJS and ESM)
- CJS default export unwrapped automatically for consistent API

This fixes errors when external modules set "type":"module" in their
package.json. Those modules must still rename installer.js to
installer.cjs if it uses require() internally.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Davor Racić 2026-02-08 10:11:24 +01:00
parent bf8d1e8022
commit 6fd5db4aed
1 changed files with 19 additions and 4 deletions

View File

@ -1277,16 +1277,31 @@ class ModuleManager {
}
}
const installerPath = path.join(sourcePath, '_module-installer', 'installer.js');
const installerDir = path.join(sourcePath, '_module-installer');
// Prefer .cjs (always CommonJS) then fall back to .js
const cjsPath = path.join(installerDir, 'installer.cjs');
const jsPath = path.join(installerDir, 'installer.js');
const hasCjs = await fs.pathExists(cjsPath);
const installerPath = hasCjs ? cjsPath : jsPath;
// Check if module has a custom installer
if (!(await fs.pathExists(installerPath))) {
if (!hasCjs && !(await fs.pathExists(jsPath))) {
return; // No custom installer
}
try {
// Load the module installer
const moduleInstaller = require(installerPath);
// .cjs files are always CommonJS and safe to require().
// .js files may be ESM (when the package sets "type":"module"),
// so use dynamic import() which handles both CJS and ESM.
let moduleInstaller;
if (hasCjs) {
moduleInstaller = require(installerPath);
} else {
const { pathToFileURL } = require('node:url');
const imported = await import(pathToFileURL(installerPath).href);
// CJS module.exports lands on .default, ESM named exports are top-level
moduleInstaller = imported.default && typeof imported.default === 'object' ? imported.default : imported;
}
if (typeof moduleInstaller.install === 'function') {
// Get project root (parent of bmad directory)