Skip to content

fix: resolve ERR_REQUIRE_ESM for inquirer v9+ (#1197)#1278

Merged
bmadcode merged 1 commit intobmad-code-org:mainfrom
Q00:main
Jan 8, 2026
Merged

fix: resolve ERR_REQUIRE_ESM for inquirer v9+ (#1197)#1278
bmadcode merged 1 commit intobmad-code-org:mainfrom
Q00:main

Conversation

@Q00
Copy link
Copy Markdown
Contributor

@Q00 Q00 commented Jan 8, 2026

What

Convert require('inquirer') to dynamic import('inquirer') in 8 CLI files

Why

Inquirer v9+ is ESM-only, causing ERR_REQUIRE_ESM crash during installation
Fixes #1197

How

  • Added lazy-loading helper in ui.js and config-collector.js
  • Converted inline requires to dynamic imports in remaining files
  • Removed unused top-level require in installer.js

Testing

Tested npx ./bmad-method-6.0.0-alpha.22.tgz install on macOS and Windows - all prompts working

Screenshots

  • macOS
image - Windows cap2

Inquirer v9+ is ESM-only, causing ERR_REQUIRE_ESM when loaded via
require() in CommonJS. Convert all require('inquirer') calls to
dynamic import('inquirer') across 8 CLI files.

Fixes bmad-code-org#1197
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Jan 8, 2026

📝 Walkthrough

Walkthrough

This PR converts static CommonJS require('inquirer') calls to dynamic ES module imports using await import('inquirer') across eight CLI and installer files, addressing ERR_REQUIRE_ESM errors when using ESM-only inquirer package.

Changes

Cohort / File(s) Summary
CLI Commands
tools/cli/commands/install.js
Replaced static top-level require('inquirer') with dynamic await import('inquirer') right before prompt invocation
Core Installer Library
tools/cli/installers/lib/core/config-collector.js, tools/cli/installers/lib/core/installer.js
Introduced lazy-loading helper function and replaced static requires with dynamic imports at call sites where inquirer prompts are needed
IDE-specific Installers
tools/cli/installers/lib/ide/antigravity.js, tools/cli/installers/lib/ide/claude-code.js, tools/cli/installers/lib/ide/codex.js, tools/cli/installers/lib/ide/github-copilot.js
Replaced CommonJS require('inquirer') with dynamic await import('inquirer') at multiple prompt invocation sites
UI Library
tools/cli/lib/ui.js
Added private getInquirer() helper with caching; updated 13 methods to lazily load inquirer via await getInquirer() before prompting operations

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related issues

  • Bad release? #1197: Directly addresses the ERR_REQUIRE_ESM crash when installing with ESM-only inquirer; implements the proposed fix of replacing CommonJS requires with dynamic imports across the installer codebase.
🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title directly addresses the main change: resolving ERR_REQUIRE_ESM for inquirer v9+ by converting to dynamic imports.
Linked Issues check ✅ Passed The PR successfully implements the dynamic import solution proposed in issue #1197, converting all CLI files from require('inquirer') to dynamic import('inquirer') to support inquirer v9+.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing the inquirer ESM compatibility issue across eight CLI files; no unrelated modifications are present.
Description check ✅ Passed The PR description clearly explains what was changed (converted require to dynamic import), why it was needed (inquirer v9+ is ESM-only causing ERR_REQUIRE_ESM), and how it was fixed across 8 CLI files, with testing on macOS and Windows.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In @tools/cli/commands/install.js:
- Line 74: The dynamic import of inquirer (const { default: inquirer } = await
import('inquirer');) lacks error handling and is inconsistent with other files
that use getInquirer(); wrap the dynamic import in a try/catch to catch import
failures and emit a clear error message (or exit gracefully) using the same
logger/exit pattern used elsewhere, and either replace the inline import with
the shared getInquirer() helper to match config-collector.js and ui.js or add a
short comment explaining why an inline import is used for this single-use case
for consistency.

In @tools/cli/installers/lib/core/config-collector.js:
- Around line 8-15: The dynamic import inside getInquirer currently can reject
and cause an unhelpful crash; wrap the await import('inquirer') in a try/catch
inside getInquirer, set _inquirer only on success, and on failure log a clear,
actionable message including the original error (e.g., via console.error or the
module's logger) and either rethrow a new Error with context or terminate
gracefully so callers don't get an unhandled rejection.

In @tools/cli/lib/ui.js:
- Around line 8-15: The dynamic import in getInquirer currently has no error
handling, so failures will cause unhandled rejections; wrap the
import('inquirer') call in a try/catch inside getInquirer, set _inquirer only on
success, and on failure log or throw a clear, actionable error (e.g., using
console.error or a project logger) that mentions inquirer failed to load and
suggests reinstalling the package, then rethrow or return a controlled fallback
so callers of getInquirer (the 13 methods using it) receive a predictable error
rather than an unhandled rejection.
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8e165b9 and 17c9d26.

📒 Files selected for processing (8)
  • tools/cli/commands/install.js
  • tools/cli/installers/lib/core/config-collector.js
  • tools/cli/installers/lib/core/installer.js
  • tools/cli/installers/lib/ide/antigravity.js
  • tools/cli/installers/lib/ide/claude-code.js
  • tools/cli/installers/lib/ide/codex.js
  • tools/cli/installers/lib/ide/github-copilot.js
  • tools/cli/lib/ui.js
🧰 Additional context used
📓 Path-based instructions (2)
**/*

⚙️ CodeRabbit configuration file

**/*: Focus on inconsistencies, contradictions, edge cases and serious issues.
Avoid commenting on minor issues such as linting, formatting and style issues.
When providing code suggestions, use GitHub's suggestion format:

<code changes>

Files:

  • tools/cli/commands/install.js
  • tools/cli/installers/lib/ide/codex.js
  • tools/cli/installers/lib/ide/github-copilot.js
  • tools/cli/installers/lib/ide/claude-code.js
  • tools/cli/installers/lib/ide/antigravity.js
  • tools/cli/lib/ui.js
  • tools/cli/installers/lib/core/config-collector.js
  • tools/cli/installers/lib/core/installer.js
**/*.js

⚙️ CodeRabbit configuration file

**/*.js: CLI tooling code. Check for: missing error handling on fs operations,
path.join vs string concatenation, proper cleanup in error paths.
Flag any process.exit() without error message.

Files:

  • tools/cli/commands/install.js
  • tools/cli/installers/lib/ide/codex.js
  • tools/cli/installers/lib/ide/github-copilot.js
  • tools/cli/installers/lib/ide/claude-code.js
  • tools/cli/installers/lib/ide/antigravity.js
  • tools/cli/lib/ui.js
  • tools/cli/installers/lib/core/config-collector.js
  • tools/cli/installers/lib/core/installer.js
🧬 Code graph analysis (5)
tools/cli/installers/lib/ide/github-copilot.js (1)
tools/cli/commands/install.js (1)
  • inquirer (74-74)
tools/cli/installers/lib/ide/claude-code.js (1)
tools/cli/commands/install.js (1)
  • inquirer (74-74)
tools/cli/installers/lib/ide/antigravity.js (1)
tools/cli/commands/install.js (1)
  • inquirer (74-74)
tools/cli/lib/ui.js (2)
tools/cli/installers/lib/core/config-collector.js (1)
  • _inquirer (9-9)
tools/cli/commands/install.js (1)
  • inquirer (74-74)
tools/cli/installers/lib/core/config-collector.js (3)
tools/cli/lib/ui.js (1)
  • _inquirer (9-9)
tools/cli/commands/install.js (1)
  • inquirer (74-74)
tools/cli/lib/yaml-format.js (1)
  • initializeModules (10-14)
🔇 Additional comments (14)
tools/cli/installers/lib/ide/github-copilot.js (1)

24-24: LGTM - Dynamic import correctly implemented.

The conversion from static require('inquirer') to dynamic await import('inquirer') correctly addresses the ERR_REQUIRE_ESM issue with inquirer v9+. The destructuring of the default export is correct, and the function is already async.

tools/cli/installers/lib/ide/claude-code.js (3)

60-60: LGTM - Dynamic import correctly implemented.

The dynamic import is properly placed inside the async function and correctly destructures the default export.


308-308: LGTM - Consistent dynamic import pattern.

The dynamic import follows the same pattern as other locations in this file and correctly addresses the ESM compatibility issue.


345-345: LGTM - Dynamic import correctly implemented.

The lazy-loading approach ensures inquirer is only loaded when the prompt is actually needed.

tools/cli/installers/lib/ide/codex.js (1)

24-24: LGTM - Dynamic import correctly implemented.

The conversion to dynamic import is correct and consistent with the pattern used across other IDE setup files. This properly resolves the ERR_REQUIRE_ESM issue for inquirer v9+.

tools/cli/installers/lib/ide/antigravity.js (3)

61-61: LGTM - Dynamic import correctly implemented.

The dynamic import is properly placed and correctly structured for ESM compatibility.


300-300: LGTM - Consistent dynamic import pattern.

The lazy-loading approach is appropriate for this interactive configuration path.


337-337: LGTM - Dynamic import correctly implemented.

The dynamic import ensures inquirer is only loaded when prompting is actually needed.

tools/cli/installers/lib/core/installer.js (3)

2142-2142: LGTM - Dynamic import correctly implemented.

The conversion from static require to dynamic import is correct and addresses the ERR_REQUIRE_ESM issue with inquirer v9+.


2159-2159: LGTM - Dynamic import correctly implemented.

The lazy-loading pattern ensures inquirer is only loaded when the legacy V4 migration prompt is actually needed.


2440-2440: LGTM - Dynamic import correctly implemented.

The dynamic import is properly structured and ensures inquirer is loaded only when handling missing custom sources that require user interaction.

tools/cli/installers/lib/core/config-collector.js (2)

186-186: LGTM!

Properly defers inquirer loading until needed. The method is already async, so the await is correct.


505-505: LGTM!

Properly defers inquirer loading until needed. The method is already async, so the await is correct.

tools/cli/lib/ui.js (1)

26-26: LGTM!

All 13 usage sites properly defer inquirer loading until needed. The methods are already async, so the await calls are correct. The lazy-loading pattern prevents ERR_REQUIRE_ESM on initial module load.

Also applies to: 462-462, 595-595, 620-620, 652-652, 759-759, 840-840, 861-861, 918-918, 1105-1105, 1256-1256, 1394-1394, 1589-1589

console.log(chalk.dim(' • ElevenLabs AI (150+ premium voices)'));
console.log(chalk.dim(' • Piper TTS (50+ free voices)\n'));

const { default: inquirer } = await import('inquirer');
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add error handling and consider consistency with other files.

Two concerns:

  1. Missing error handling: If import('inquirer') fails, the process will crash with an unclear error message rather than gracefully explaining the problem.

  2. Pattern inconsistency: This file uses inline await import('inquirer') while config-collector.js and ui.js use a lazy-loading helper function getInquirer(). For a single-use case like this, inline is acceptable, but the inconsistency may confuse future maintainers.

🛡️ Proposed fix to add error handling
-          const { default: inquirer } = await import('inquirer');
+          let inquirer;
+          try {
+            inquirer = (await import('inquirer')).default;
+          } catch (error) {
+            console.error(chalk.red('Failed to load inquirer module:'), error.message);
+            console.error(chalk.dim('Try running: npm install inquirer'));
+            process.exit(1);
+          }
           await inquirer.prompt([

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In @tools/cli/commands/install.js at line 74, The dynamic import of inquirer
(const { default: inquirer } = await import('inquirer');) lacks error handling
and is inconsistent with other files that use getInquirer(); wrap the dynamic
import in a try/catch to catch import failures and emit a clear error message
(or exit gracefully) using the same logger/exit pattern used elsewhere, and
either replace the inline import with the shared getInquirer() helper to match
config-collector.js and ui.js or add a short comment explaining why an inline
import is used for this single-use case for consistency.

Comment on lines +8 to +15
// Lazy-load inquirer (ESM module) to avoid ERR_REQUIRE_ESM
let _inquirer = null;
async function getInquirer() {
if (!_inquirer) {
_inquirer = (await import('inquirer')).default;
}
return _inquirer;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add error handling for the dynamic import.

If import('inquirer') fails (e.g., package not installed, corrupted node_modules), the unhandled rejection will crash the process with a cryptic error rather than a helpful message.

🛡️ Proposed fix to add error handling
 // Lazy-load inquirer (ESM module) to avoid ERR_REQUIRE_ESM
 let _inquirer = null;
 async function getInquirer() {
   if (!_inquirer) {
-    _inquirer = (await import('inquirer')).default;
+    try {
+      _inquirer = (await import('inquirer')).default;
+    } catch (error) {
+      console.error(chalk.red('Failed to load inquirer module:'), error.message);
+      console.error(chalk.dim('Try running: npm install inquirer'));
+      process.exit(1);
+    }
   }
   return _inquirer;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Lazy-load inquirer (ESM module) to avoid ERR_REQUIRE_ESM
let _inquirer = null;
async function getInquirer() {
if (!_inquirer) {
_inquirer = (await import('inquirer')).default;
}
return _inquirer;
}
// Lazy-load inquirer (ESM module) to avoid ERR_REQUIRE_ESM
let _inquirer = null;
async function getInquirer() {
if (!_inquirer) {
try {
_inquirer = (await import('inquirer')).default;
} catch (error) {
console.error(chalk.red('Failed to load inquirer module:'), error.message);
console.error(chalk.dim('Try running: npm install inquirer'));
process.exit(1);
}
}
return _inquirer;
}
🤖 Prompt for AI Agents
In @tools/cli/installers/lib/core/config-collector.js around lines 8 - 15, The
dynamic import inside getInquirer currently can reject and cause an unhelpful
crash; wrap the await import('inquirer') in a try/catch inside getInquirer, set
_inquirer only on success, and on failure log a clear, actionable message
including the original error (e.g., via console.error or the module's logger)
and either rethrow a new Error with context or terminate gracefully so callers
don't get an unhandled rejection.

Comment on lines +8 to +15
// Lazy-load inquirer (ESM module) to avoid ERR_REQUIRE_ESM
let _inquirer = null;
async function getInquirer() {
if (!_inquirer) {
_inquirer = (await import('inquirer')).default;
}
return _inquirer;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add error handling for the dynamic import.

If import('inquirer') fails (e.g., package not installed, corrupted node_modules), the unhandled rejection will crash the process with a cryptic error rather than a helpful message. Given this helper is called by 13 different methods in this file, proper error handling is essential.

🛡️ Proposed fix to add error handling
 // Lazy-load inquirer (ESM module) to avoid ERR_REQUIRE_ESM
 let _inquirer = null;
 async function getInquirer() {
   if (!_inquirer) {
-    _inquirer = (await import('inquirer')).default;
+    try {
+      _inquirer = (await import('inquirer')).default;
+    } catch (error) {
+      console.error(chalk.red('Failed to load inquirer module:'), error.message);
+      console.error(chalk.dim('Try running: npm install inquirer'));
+      process.exit(1);
+    }
   }
   return _inquirer;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Lazy-load inquirer (ESM module) to avoid ERR_REQUIRE_ESM
let _inquirer = null;
async function getInquirer() {
if (!_inquirer) {
_inquirer = (await import('inquirer')).default;
}
return _inquirer;
}
// Lazy-load inquirer (ESM module) to avoid ERR_REQUIRE_ESM
let _inquirer = null;
async function getInquirer() {
if (!_inquirer) {
try {
_inquirer = (await import('inquirer')).default;
} catch (error) {
console.error(chalk.red('Failed to load inquirer module:'), error.message);
console.error(chalk.dim('Try running: npm install inquirer'));
process.exit(1);
}
}
return _inquirer;
}
🤖 Prompt for AI Agents
In @tools/cli/lib/ui.js around lines 8 - 15, The dynamic import in getInquirer
currently has no error handling, so failures will cause unhandled rejections;
wrap the import('inquirer') call in a try/catch inside getInquirer, set
_inquirer only on success, and on failure log or throw a clear, actionable error
(e.g., using console.error or a project logger) that mentions inquirer failed to
load and suggests reinstalling the package, then rethrow or return a controlled
fallback so callers of getInquirer (the 13 methods using it) receive a
predictable error rather than an unhandled rejection.

@alexeyv
Copy link
Copy Markdown
Collaborator

alexeyv commented Jan 8, 2026

I really hate mixing requires and imports. Isn't there a version of "downgrade something back until it doesn't need to be ESM", followed by a somewhat bigger "switch the whole shebang to ESM"?

@Q00
Copy link
Copy Markdown
Contributor Author

Q00 commented Jan 8, 2026

I really hate mixing requires and imports. Isn't there a version of "downgrade something back until it doesn't need to be ESM", followed by a somewhat bigger "switch the whole shebang to ESM"?

@alexeyv
Totally understand the concern about mixing requires and imports.

For this PR, I went with dynamic imports because:

  • inquirer v8 had other issues (mentioned in the issue thread)
  • Full ESM conversion felt out of scope for a quick bug fix

That said, a proper ESM migration would definitely be cleaner. Happy to help with that if maintainers want to go that route!

@bmadcode bmadcode merged commit d19cca7 into bmad-code-org:main Jan 8, 2026
@bmadcode
Copy link
Copy Markdown
Collaborator

bmadcode commented Jan 8, 2026

thanks @Q00 - merged! Will run some tests also on mac and verify a few other scenarios and try to get this published with the next version today or tomorrow!

@alexeyv
Copy link
Copy Markdown
Collaborator

alexeyv commented Jan 8, 2026

@Q00 : if you can figure out a clean conversion of the whole thing into ESM, I'm not gonna promise you a bronze horse statue, but a reasonable degree of eternal gratitude? for sure!

@Q00
Copy link
Copy Markdown
Contributor Author

Q00 commented Jan 9, 2026

@alexeyv Challenge accepted!

I think a phased approach is best to ensure stability. Here is my 3-step plan:

  1. PoC/Spike: I'll first create a branch and attempt the structural migration myself.
  2. Draft PR: Once the baseline works, I'll open a Draft PR for your feedback.
  3. Community Testing: After that, we can gather more people from Discord to test it across different environments.

I'll get started on Step 1 and keep you posted!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bad release?

3 participants