fix(installer): seed channelOptions before module picker, not gate
CodeRabbit caught a label/install mismatch in the previous approach: the module picker resolves version labels via decideChannelForModule, which runs before _interactiveChannelGate. With channelOptions.global still null at picker time, labels rendered from stable tags — then the gate flipped global to 'next' and externals installed from main HEAD. Net effect on @next launches: "tea (v1.6.0)" in the picker, but install pulled HEAD. Move the launch detection up into promptInstall, immediately after parseChannelOptions. Seeding channelOptions.global = 'next' before the picker makes labels resolve from main HEAD (matching the install) and lets the existing gate's haveFlagIntent check skip cleanly — the @next user already declared their intent by typing it. Per-module customization remains available via --pin / --next / --channel flags, same as for any pre-set global.
This commit is contained in:
parent
30d94a878d
commit
43b29015de
|
|
@ -129,6 +129,24 @@ class UI {
|
||||||
await prompts.log.warn(warning);
|
await prompts.log.warn(warning);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// When the user launched the installer from a prerelease (npx bmad-method@next),
|
||||||
|
// mirror that intent for external modules: seed the global channel to 'next' so
|
||||||
|
// the module picker's version labels resolve from main HEAD (matching what
|
||||||
|
// actually gets installed) and the interactive channel gate skips — the user
|
||||||
|
// already declared "next" intent by typing @next. Explicit channel flags
|
||||||
|
// override this seed.
|
||||||
|
if (
|
||||||
|
semver.prerelease(installerPackageJson.version) !== null &&
|
||||||
|
!channelOptions.global &&
|
||||||
|
channelOptions.nextSet.size === 0 &&
|
||||||
|
channelOptions.pins.size === 0
|
||||||
|
) {
|
||||||
|
channelOptions.global = 'next';
|
||||||
|
await prompts.log.info(
|
||||||
|
'Launched from a prerelease — installing all external modules from main HEAD (next channel). Pass --all-stable or --pin to override.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Get directory from options or prompt
|
// Get directory from options or prompt
|
||||||
let confirmedDirectory;
|
let confirmedDirectory;
|
||||||
if (options.directory) {
|
if (options.directory) {
|
||||||
|
|
@ -331,10 +349,10 @@ class UI {
|
||||||
selectedModules.unshift('core');
|
selectedModules.unshift('core');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Interactive channel gate: "Ready to install (all stable)? [Y/n]" — or
|
// Interactive channel gate: "Ready to install (all stable)? [Y/n]"
|
||||||
// "(all next)" when the installer was launched from a prerelease
|
// Only shown for fresh installs with no channel flags and an external module
|
||||||
// (npx bmad-method@next). Only shown for fresh installs with no channel
|
// selected. Skipped for prerelease launches because channelOptions.global
|
||||||
// flags and an external module selected. Non-interactive installs skip this
|
// was already seeded to 'next' upstream. Non-interactive installs skip this
|
||||||
// and fall through to the registry default (stable) or whatever flags were
|
// and fall through to the registry default (stable) or whatever flags were
|
||||||
// supplied.
|
// supplied.
|
||||||
await this._interactiveChannelGate({ options, channelOptions, selectedModules });
|
await this._interactiveChannelGate({ options, channelOptions, selectedModules });
|
||||||
|
|
@ -1782,17 +1800,16 @@ class UI {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fast-path channel gate: confirm "all stable" / "all next" or open the
|
* Fast-path channel gate: confirm "all stable" or open the per-module picker.
|
||||||
* 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), OR
|
||||||
|
* the installer was launched from a prerelease (which seeds
|
||||||
|
* channelOptions.global = 'next' upstream in promptInstall)
|
||||||
* - no externals/community modules are selected
|
* - no externals/community modules are selected
|
||||||
*
|
*
|
||||||
* Mutates channelOptions.global / .pins / .nextSet to reflect gate + picker choices.
|
* Mutates channelOptions.pins and channelOptions.nextSet to reflect picker choices.
|
||||||
*/
|
*/
|
||||||
async _interactiveChannelGate({ options, channelOptions, selectedModules }) {
|
async _interactiveChannelGate({ options, channelOptions, selectedModules }) {
|
||||||
if (options.yes) return;
|
if (options.yes) return;
|
||||||
|
|
@ -1818,24 +1835,11 @@ 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 ${fastPathChannel})? Pick "n" to customize channels or pin versions.`,
|
message: `Ready to install (all stable)? Pick "n" to customize channels or pin versions.`,
|
||||||
default: true,
|
default: true,
|
||||||
});
|
});
|
||||||
if (fastPath) {
|
if (fastPath) return; // stable for all, registry default applies
|
||||||
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');
|
||||||
|
|
@ -1865,7 +1869,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: fastPathChannel,
|
default: 'stable',
|
||||||
});
|
});
|
||||||
|
|
||||||
if (choice === 'next') {
|
if (choice === 'next') {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue