fix(installer): mirror launch channel as default for external modules

When the user runs `npx bmad-method@next install`, the installer itself
runs from a prerelease, but the interactive channel gate previously hardcoded
"(all stable)" — defaulting tea/community modules to stable while bmad-method
itself was on next. The bleeding-edge launch did not flow through.

Detect the installer's own version via semver.prerelease() and default the
gate (and per-module picker) to match — "all next" for prerelease launches,
"all stable" for stable. Users keep full control: hit "n" to customize per
module, or pass explicit --channel / --pin / --next flags to override.
This commit is contained in:
Brian Madison 2026-04-26 10:41:47 -05:00
parent 7baa30c567
commit 30d94a878d
1 changed files with 28 additions and 9 deletions

View File

@ -2,6 +2,7 @@ const path = require('node:path');
const os = require('node:os'); const os = require('node:os');
const semver = require('semver'); const semver = require('semver');
const fs = require('./fs-native'); const fs = require('./fs-native');
const installerPackageJson = require('../../package.json');
const { CLIUtils } = require('./cli-utils'); const { CLIUtils } = require('./cli-utils');
const { ExternalModuleManager } = require('./modules/external-manager'); const { ExternalModuleManager } = require('./modules/external-manager');
const { resolveModuleVersion } = require('./modules/version-resolver'); const { resolveModuleVersion } = require('./modules/version-resolver');
@ -330,10 +331,12 @@ class UI {
selectedModules.unshift('core'); selectedModules.unshift('core');
} }
// Interactive channel gate: "Ready to install (all stable)? [Y/n]" // Interactive channel gate: "Ready to install (all stable)? [Y/n]" — or
// Only shown for fresh installs with no channel flags and an external module // "(all next)" when the installer was launched from a prerelease
// selected. Non-interactive installs skip this and fall through to the // (npx bmad-method@next). Only shown for fresh installs with no channel
// registry default (stable) or whatever flags were supplied. // flags and an external module selected. Non-interactive installs skip this
// and fall through to the registry default (stable) or whatever flags were
// supplied.
await this._interactiveChannelGate({ options, channelOptions, selectedModules }); await this._interactiveChannelGate({ options, channelOptions, selectedModules });
let toolSelection = await this.promptToolSelection(confirmedDirectory, options); let toolSelection = await this.promptToolSelection(confirmedDirectory, options);
@ -1779,14 +1782,17 @@ class UI {
} }
/** /**
* Fast-path channel gate: confirm "all stable" or open the per-module picker. * Fast-path channel gate: confirm "all stable" / "all next" or open the
* per-module picker. The default channel mirrors the installer's launch tag
* a prerelease bmad-method (npx bmad-method@next) defaults the gate to "all
* next"; a stable launch keeps the original "all stable" default.
* *
* Skipped when: * Skipped when:
* - running non-interactively (--yes) * - running non-interactively (--yes)
* - the user already passed channel flags (--channel / --pin / --next) * - the user already passed channel flags (--channel / --pin / --next)
* - no externals/community modules are selected * - no externals/community modules are selected
* *
* Mutates channelOptions.pins and channelOptions.nextSet to reflect picker choices. * Mutates channelOptions.global / .pins / .nextSet to reflect gate + picker choices.
*/ */
async _interactiveChannelGate({ options, channelOptions, selectedModules }) { async _interactiveChannelGate({ options, channelOptions, selectedModules }) {
if (options.yes) return; if (options.yes) return;
@ -1812,11 +1818,24 @@ class UI {
}); });
if (channelSelectable.length === 0) return; if (channelSelectable.length === 0) return;
// When the user launched the installer from a prerelease (npx bmad-method@next),
// mirror that intent for external modules: default the gate to "all next" so the
// bleeding-edge launch flows end-to-end. Stable launches keep the original
// "all stable" default.
const launchedFromPrerelease = semver.prerelease(installerPackageJson.version) !== null;
const fastPathChannel = launchedFromPrerelease ? 'next' : 'stable';
const fastPath = await prompts.confirm({ const fastPath = await prompts.confirm({
message: `Ready to install (all stable)? Pick "n" to customize channels or pin versions.`, message: `Ready to install (all ${fastPathChannel})? Pick "n" to customize channels or pin versions.`,
default: true, default: true,
}); });
if (fastPath) return; // stable for all, registry default applies if (fastPath) {
if (fastPathChannel === 'next') {
channelOptions.global = 'next';
}
// stable: leave channelOptions untouched; registry default applies.
return;
}
// Customize path: per-module picker. // Customize path: per-module picker.
const { fetchStableTags, parseGitHubRepo } = require('./modules/channel-resolver'); const { fetchStableTags, parseGitHubRepo } = require('./modules/channel-resolver');
@ -1846,7 +1865,7 @@ class UI {
{ name: 'next (main HEAD \u2014 current development)', value: 'next' }, { name: 'next (main HEAD \u2014 current development)', value: 'next' },
{ name: 'pin (specific version)', value: 'pin' }, { name: 'pin (specific version)', value: 'pin' },
], ],
default: 'stable', default: fastPathChannel,
}); });
if (choice === 'next') { if (choice === 'next') {