From 6fd5db4aede40534a832ccc64586db1c1b9694fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Davor=20Raci=C4=87?= Date: Sun, 8 Feb 2026 10:11:24 +0100 Subject: [PATCH] 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 --- tools/cli/installers/lib/modules/manager.js | 23 +++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/tools/cli/installers/lib/modules/manager.js b/tools/cli/installers/lib/modules/manager.js index 0af4312fc..831b6e6f4 100644 --- a/tools/cli/installers/lib/modules/manager.js +++ b/tools/cli/installers/lib/modules/manager.js @@ -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)