Skip to content

Minor installer fixes#1590

Merged
bmadcode merged 8 commits intobmad-code-org:mainfrom
dracic:fix/installer
Feb 8, 2026
Merged

Minor installer fixes#1590
bmadcode merged 8 commits intobmad-code-org:mainfrom
dracic:fix/installer

Conversation

@dracic
Copy link
Copy Markdown
Contributor

@dracic dracic commented Feb 8, 2026

Summary

  • Remove the redundant "⚠ None - Skip module installation" option from the module selection prompt — core is always locked/selected, so users can simply submit with only core to skip modules
  • Remove ~100 lines of dead code: selectModules(), getExternalModuleChoices(), and selectExternalModules() methods that were never called
  • Add ESM and .cjs support to ModuleManager.runModuleInstaller() so external modules with "type": "module" in their package.json can provide installers in either format

Details

"None" option removal (ui.js)

The autocompleteMultiselect prompt has lockedValues: ['core'] and required: true, meaning core is always selected and cannot be unchecked. On fresh install, no other modules are pre-selected, so pressing Enter with only core selected already results in no modules being installed — making the "None" option redundant and potentially confusing.

The __NONE__ sentinel value handling and override logic have been removed. Core filtering is now done inside selectAllModules() itself rather than at call sites.

Dead code removal (ui.js)

Three methods were identified as unused (zero call sites):

  • selectModules() — superseded by selectAllModules()
  • getExternalModuleChoices() — helper for the above
  • selectExternalModules() — superseded by selectAllModules()

ESM/CJS module installer support (manager.js)

runModuleInstaller() previously used require() to load module installers. This fails when an external module's package.json sets "type": "module" (e.g., CIS), because Node.js treats .js files as ESM where require is not defined.

The loader now:

  1. Checks for installer.cjs first (always loaded via require(), safe regardless of package type)
  2. Falls back to installer.js loaded via dynamic import() (handles both CJS and ESM)
  3. Unwraps CJS default exports automatically for a consistent API

Note: External modules like CIS that use CommonJS syntax (require/module.exports) inside a "type": "module" package must still rename their installer to .cjs — this fix ensures our loader will pick it up correctly when they do.

Test plan

  • All 12 installation component tests pass
  • ESLint passes with zero warnings on modified files
  • Manual: run bmad install, select no extra modules (only core locked) → verify install completes with no modules
  • Manual: run bmad install with other module selected after renames installer.jsinstaller.cjs → verify CIS installer runs successfully

dracic and others added 2 commits February 8, 2026 10:02
The "None - Skip module installation" option was unnecessary since
core is always locked/selected, satisfying the required constraint.
Users can simply press Enter with only core selected to skip modules.
Also removes dead code: selectModules(), getExternalModuleChoices(),
and selectExternalModules() methods that were never called.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>
@augmentcode
Copy link
Copy Markdown

augmentcode bot commented Feb 8, 2026

🤖 Augment PR Summary

Summary: Improves the CLI installer’s module selection UX and makes external module post-installers compatible with both CommonJS and ESM module packaging.

Changes:

  • Removes the redundant “None / Skip module installation” choice since core is always locked/required and selecting only core already implies “no extra modules”.
  • Deletes unused module-selection helper methods in tools/cli/lib/ui.js to reduce dead code paths.
  • Updates module selection flow to consistently exclude core from the returned module list while still showing it as locked in the prompt.
  • Enhances ModuleManager.runModuleInstaller() to prefer installer.cjs (required via require()) and fall back to installer.js loaded via dynamic import() for ESM support.
  • Normalizes dynamic-import results by unwrapping default exports so installers can expose a consistent API across CJS/ESM.

Technical Notes: This addresses external modules with "type": "module" by allowing installers to be authored as ESM (.js) or forced to CJS via .cjs, avoiding require() failures in ESM packages.

🤖 Was this summary useful? React with 👍 or 👎

Copy link
Copy Markdown

@augmentcode augmentcode bot left a comment

Choose a reason for hiding this comment

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

Review completed. 2 suggestions posted.

Fix All in Augment

Comment augment review to trigger a new review at any time.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Feb 8, 2026

📝 Walkthrough

Walkthrough

Refactors installer loading to prefer CommonJS (installer.cjs) with fallback to ESM (installer.js) via dynamic import, and simplifies CLI UI by removing module-selection helper methods and adjusting selection logic to exclude core (removing None/skip sentinel behavior).

Changes

Cohort / File(s) Summary
Installer Module Loading Strategy
tools/cli/installers/lib/modules/manager.js
Adds installerDir, cjsPath, jsPath, and hasCjs; prefers loading installer.cjs via require() and falls back to installer.js via import() + export normalization. Adjusts existence checks and error handling around optional post-install scripts.
UI Module Selection Refactor
tools/cli/lib/ui.js
Removes selectModules, getExternalModuleChoices, and selectExternalModules. Simplifies selection flow by eliminating None/NONE sentinel options and ensuring core is shown locked but excluded from final results.

Sequence Diagram(s)

sequenceDiagram
    rect rgba(52,152,219,0.5)
    participant CLI as CLI UI
    end
    rect rgba(46,204,113,0.5)
    participant Manager as Installer Manager
    end
    rect rgba(241,196,15,0.5)
    participant FS as Filesystem
    end
    rect rgba(155,89,182,0.5)
    participant Module as Installer Module
    end

    CLI->>Manager: request runModuleInstaller(module)
    Manager->>FS: check for installer.cjs and installer.js
    FS-->>Manager: existence info
    alt installer.cjs exists
        Manager->>Module: require(installer.cjs)
    else installer.js exists
        Manager->>Module: import(pathToFileURL(installer.js))
    else none
        Manager-->>CLI: skip custom installer
    end
    Module-->>Manager: installer object (CJS or normalized ESM)
    Manager->>Module: call installer.install(...) if present
    Manager-->>CLI: continue post-install flow
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • bmadcode
🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive The PR title 'Minor installer fixes' is related to the main changes but is overly broad and generic, not clearly summarizing the specific work done (ESM/CJS support, dead code removal, UI simplification). Consider a more descriptive title like 'Support ESM/CJS installers and remove dead module selection code' to better convey the key changes.
✅ Passed checks (2 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description check ✅ Passed The pull request description clearly explains the changes: removing a redundant UI option, deleting dead code, and adding ESM/.cjs support to module installers.

✏️ 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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tools/cli/lib/ui.js (1)

268-277: ⚠️ Potential issue | 🟡 Minor

The update path does not filter 'core' from CLI-provided --modules.

In the new-install path (line 467), selectedModules is filtered to remove 'core'. In the update path here, options.modules parsing (line 270-274) can include 'core', but it's passed directly to the return on line 373 without filtering. Since installCore: true is always set (line 372), having 'core' in the modules list too could cause double-installation or unexpected behavior.

Pre-existing, but the inconsistency is now more visible after this PR's filter changes.

Proposed fix
         selectedModules = await this.selectAllModules(installedModuleIds);
         }
 
+        // Filter out core - it's always installed via installCore flag
+        selectedModules = selectedModules.filter((m) => m !== 'core');
+
         // After module selection, ask about custom modules
🤖 Fix all issues with AI agents
In `@tools/cli/installers/lib/modules/manager.js`:
- Around line 1280-1304: The dynamic-import branch incorrectly unwraps default
exports only when typeof imported.default === 'object', which misses
function/class defaults; change the unwrap logic in the else branch where
imported is obtained via import(pathToFileURL(installerPath).href) so that
moduleInstaller is set to imported.default when imported.default is defined
(e.g., imported.default !== undefined && imported.default !== null) and
otherwise set to imported; update references around moduleInstaller, imported
and installerPath accordingly so .install checks work for default-exported
functions/classes as well as objects.
🧹 Nitpick comments (1)
tools/cli/installers/lib/modules/manager.js (1)

1293-1298: require() result is not checked for the .cjs path — unlike the .js branch, there's no default-export unwrapping.

If a .cjs installer uses module.exports = { default: { install } } (e.g., transpiled from ESM), moduleInstaller.install won't exist — you'd need moduleInstaller.default.install. The .js branch handles this unwrapping, but the .cjs branch doesn't. This is likely fine for hand-written CJS modules, but could bite if someone transpiles ESM to CJS.

Low risk given the audience, but worth a defensive note or a shared unwrap step.

- Filter 'core' from CLI --modules in update path for consistency
- Update selectAllModules() JSDoc to reflect core exclusion
- Fix ESM default-export unwrap to handle function/class exports

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Change error display from log.error to log.warn and explain that the
module was installed successfully — only the optional post-install
script could not run. Prevents users from thinking the module
installation itself failed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@dracic dracic marked this pull request as draft February 8, 2026 12:46
Post-install scripts fail due to CJS/ESM incompatibility but module
files are already copied successfully. Silently catch the error instead
of showing a warning that alarms users into thinking installation failed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@dracic dracic marked this pull request as ready for review February 8, 2026 14:52
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Feb 8, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Feb 8, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@augmentcode augmentcode bot left a comment

Choose a reason for hiding this comment

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

Review completed. 1 suggestions posted.

Fix All in Augment

Comment augment review to trigger a new review at any time.

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: 1

🤖 Fix all issues with AI agents
In `@tools/cli/installers/lib/modules/manager.js`:
- Around line 1330-1336: The empty catch block after calling
moduleInstaller.install() swallows all errors; change it to catch the error
(e.g., catch (err)) and log the failure in verbose/debug mode including the
error and context (module name and that it was a post-install step) so CJS/ESM
incompatibility remains silent by default but real runtime/IO errors are
diagnosable; keep the flow that post-install scripts are optional (do not
re-throw by default), but ensure the catch uses the existing verbose/logger API
(or console.debug if no logger available) to record err and moduleInstaller
information.
🧹 Nitpick comments (2)
tools/cli/lib/ui.js (1)

364-366: Redundant core filter — selectAllModules already strips it at Line 987.

Lines 365, 470, and 987 all filter m !== 'core'. The one at 987 inside selectAllModules is the canonical location. The filters at 365 and 470 only matter for the options.modules (CLI) and options.yes code paths, which bypass selectAllModules. So they are necessary for those paths but redundant when selectAllModules is the source.

Consider extracting a small helper (e.g., filterCoreModule(modules)) to centralize this, reducing the chance that a future code path forgets the filter.

tools/cli/installers/lib/modules/manager.js (1)

1292-1304: Loading and execution share one try/catch — a load failure is indistinguishable from an install failure.

Lines 1292–1328 load the module and call .install() inside the same try. If loading fails (e.g., syntax error in the installer file), the catch silently discards it. If .install() fails, same silent discard. Separating these would let you surface load errors (which likely indicate a packaging bug) while still tolerating install-script failures.

Sketch
     let moduleInstaller;
+    try {
       if (hasCjs) {
         moduleInstaller = require(installerPath);
       } else {
         const { pathToFileURL } = require('node:url');
         const imported = await import(pathToFileURL(installerPath).href);
         moduleInstaller = imported.default == null ? imported : imported.default;
       }
+    } catch (loadError) {
+      if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
+        await prompts.log.warn(`  Could not load installer for ${moduleName}: ${loadError.message}`);
+      }
+      return;
+    }

+    try {
       if (typeof moduleInstaller.install === 'function') {
         // ... existing call ...
       }
-    } catch {
+    } catch (runError) {
+      if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
+        await prompts.log.warn(`  Post-install script for ${moduleName} failed: ${runError.message}`);
+      }
     }

dracic and others added 2 commits February 8, 2026 15:59
The checkmark list already shows each installed module and IDE tool.
Keep only the install path and file-warning lines in the summary footer.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@dracic dracic mentioned this pull request Feb 8, 2026
2 tasks
@wsmoak
Copy link
Copy Markdown
Contributor

wsmoak commented Feb 8, 2026

I saw the error described in #1592 and came to report it but found it's already fixed. Pulled this branch to a git worktree to try it out. Installed all the things for a few tools with no installer errors. QA Passed :)

◇  Installation complete
│
◇  BMAD is ready to use! ───────────────────────────────────────────────╮
│                                                                       │
│    ✓  Core (installed)                                                │
│    ✓  Module: bmm (installed)                                         │
│    ✓  Module: bmb (installed)                                         │
│    ✓  Module: cis (installed)                                         │
│    ✓  Module: gds (installed)                                         │
│    ✓  Module: tea (installed)                                         │
│    ✓  Configurations (generated)                                      │
│    ✓  Manifests (82 workflows, 27 agents, 6 tasks, 0 tools)           │
│    ✓  Help catalog                                                    │
│    ✓  claude-code (27 agents, 74 workflows, 6 tasks)                  │
│    ✓  codex (27 agents, 74 workflows, 6 tasks)                        │
│    ✓  gemini (27 agents, 74 workflows, 6 tasks)                       │
│    ✓  github-copilot (27 agents)                                      │
│    ✓  Module installers                                               │
│                                                                       │
│    Installed to: /Users/wsmoak/Projects/deleteme-20260208-1334/_bmad  │
│                                                                       │
├───────────────────────────────────────────────────────────────────────╯

@dracic
Copy link
Copy Markdown
Contributor Author

dracic commented Feb 8, 2026

Thanks @wsmoak! Details are described at CIS: bmad-code-org/bmad-module-creative-intelligence-suite#4

@bmadcode bmadcode merged commit 90ea3cb into bmad-code-org:main Feb 8, 2026
5 checks passed
@dracic dracic deleted the fix/installer branch February 9, 2026 06:18
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.

3 participants