|
|
|
@ -26,9 +26,10 @@ const choiceUtils = { Separator };
|
|
|
|
class UI {
|
|
|
|
class UI {
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* Prompt for installation configuration
|
|
|
|
* Prompt for installation configuration
|
|
|
|
|
|
|
|
* @param {Object} options - CLI options object (may contain directory property)
|
|
|
|
* @returns {Object} Installation configuration
|
|
|
|
* @returns {Object} Installation configuration
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
async promptInstall() {
|
|
|
|
async promptInstall(options = {}) {
|
|
|
|
CLIUtils.displayLogo();
|
|
|
|
CLIUtils.displayLogo();
|
|
|
|
|
|
|
|
|
|
|
|
// Display version-specific start message from install-messages.yaml
|
|
|
|
// Display version-specific start message from install-messages.yaml
|
|
|
|
@ -36,7 +37,7 @@ class UI {
|
|
|
|
const messageLoader = new MessageLoader();
|
|
|
|
const messageLoader = new MessageLoader();
|
|
|
|
messageLoader.displayStartMessage();
|
|
|
|
messageLoader.displayStartMessage();
|
|
|
|
|
|
|
|
|
|
|
|
const confirmedDirectory = await this.getConfirmedDirectory();
|
|
|
|
const confirmedDirectory = await this.getConfirmedDirectory(options);
|
|
|
|
|
|
|
|
|
|
|
|
// Preflight: Check for legacy BMAD v4 footprints immediately after getting directory
|
|
|
|
// Preflight: Check for legacy BMAD v4 footprints immediately after getting directory
|
|
|
|
const { Detector } = require('../installers/lib/core/detector');
|
|
|
|
const { Detector } = require('../installers/lib/core/detector');
|
|
|
|
@ -435,95 +436,29 @@ class UI {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
// NEW INSTALL: Show recommended tools first with "Browse All" option
|
|
|
|
// NEW INSTALL: Show all tools with search
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
const recommendedOptions = preferredIdes.map((ide) => {
|
|
|
|
const allTools = [...preferredIdes, ...otherIdes];
|
|
|
|
const isConfigured = configuredPreferred.includes(ide.value);
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
|
|
label: isConfigured ? `${ide.name} ⭐ ✅` : `${ide.name} ⭐`,
|
|
|
|
|
|
|
|
value: ide.value,
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Add "browse all" option at the end if there are additional tools
|
|
|
|
const allToolOptions = allTools.map((ide) => {
|
|
|
|
if (otherIdes.length > 0) {
|
|
|
|
|
|
|
|
const totalTools = preferredIdes.length + otherIdes.length;
|
|
|
|
|
|
|
|
recommendedOptions.push({
|
|
|
|
|
|
|
|
label: `→ Browse all supported tools (${totalTools} total)...`,
|
|
|
|
|
|
|
|
value: '__BROWSE_ALL__',
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Pre-select previously configured preferred tools
|
|
|
|
|
|
|
|
const recommendedInitialValues = configuredPreferred.length > 0 ? configuredPreferred : undefined;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const recommendedSelected = await prompts.multiselect({
|
|
|
|
|
|
|
|
message: `Integrate with ${chalk.dim('(↑/↓ to navigate • SPACE: select • ENTER: confirm)')}:`,
|
|
|
|
|
|
|
|
options: recommendedOptions,
|
|
|
|
|
|
|
|
initialValues: recommendedInitialValues,
|
|
|
|
|
|
|
|
required: false,
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const selectedRecommended = recommendedSelected || [];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
// STEP 2: Handle "Browse All" selection - show additional tools if requested
|
|
|
|
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
const wantsBrowseAll = selectedRecommended.includes('__BROWSE_ALL__');
|
|
|
|
|
|
|
|
const filteredRecommended = selectedRecommended.filter((v) => v !== '__BROWSE_ALL__');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Show additional tools if:
|
|
|
|
|
|
|
|
// 1. User explicitly chose "Browse All", OR
|
|
|
|
|
|
|
|
// 2. User has previously configured "other" tools, OR
|
|
|
|
|
|
|
|
// 3. User selected no recommended tools (allow them to pick from other tools)
|
|
|
|
|
|
|
|
const showAdditionalTools = wantsBrowseAll || configuredOther.length > 0 || filteredRecommended.length === 0;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let selectedAdditionalOrAll = [];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (showAdditionalTools) {
|
|
|
|
|
|
|
|
// Show ALL tools if:
|
|
|
|
|
|
|
|
// - User explicitly chose "Browse All", OR
|
|
|
|
|
|
|
|
// - User selected nothing from recommended (so they can pick from everything)
|
|
|
|
|
|
|
|
// Otherwise, show only "other" tools as additional options
|
|
|
|
|
|
|
|
const showAllTools = wantsBrowseAll || filteredRecommended.length === 0;
|
|
|
|
|
|
|
|
const toolsToShow = showAllTools ? [...preferredIdes, ...otherIdes] : otherIdes;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (toolsToShow.length > 0) {
|
|
|
|
|
|
|
|
const allToolOptions = toolsToShow.map((ide) => {
|
|
|
|
|
|
|
|
const isConfigured = configuredIdes.includes(ide.value);
|
|
|
|
|
|
|
|
const isPreferred = preferredIdes.some((p) => p.value === ide.value);
|
|
|
|
const isPreferred = preferredIdes.some((p) => p.value === ide.value);
|
|
|
|
let label = ide.name;
|
|
|
|
let label = ide.name;
|
|
|
|
if (isPreferred) label += ' ⭐';
|
|
|
|
if (isPreferred) label += ' ⭐';
|
|
|
|
if (isConfigured) label += ' ✅';
|
|
|
|
|
|
|
|
return {
|
|
|
|
return {
|
|
|
|
label,
|
|
|
|
label,
|
|
|
|
value: ide.value,
|
|
|
|
value: ide.value,
|
|
|
|
};
|
|
|
|
};
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// Pre-select: previously configured tools + any recommended tools already selected
|
|
|
|
const selectedIdes = await prompts.autocompleteMultiselect({
|
|
|
|
const initialValues = [...configuredIdes, ...filteredRecommended].filter((v, i, arr) => arr.indexOf(v) === i); // dedupe
|
|
|
|
message: 'Select tools:',
|
|
|
|
|
|
|
|
|
|
|
|
// Use "additional" only if user already selected some recommended tools
|
|
|
|
|
|
|
|
const isAdditional = !wantsBrowseAll && filteredRecommended.length > 0;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
console.log('');
|
|
|
|
|
|
|
|
const selected = await prompts.autocompleteMultiselect({
|
|
|
|
|
|
|
|
message: isAdditional ? 'Select additional tools:' : 'Select tools:',
|
|
|
|
|
|
|
|
options: allToolOptions,
|
|
|
|
options: allToolOptions,
|
|
|
|
initialValues: initialValues.length > 0 ? initialValues : undefined,
|
|
|
|
initialValues: configuredIdes.length > 0 ? configuredIdes : undefined,
|
|
|
|
required: false,
|
|
|
|
required: false,
|
|
|
|
maxItems: 8,
|
|
|
|
maxItems: 8,
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
selectedAdditionalOrAll = selected || [];
|
|
|
|
const allSelectedIdes = selectedIdes || [];
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Combine selections:
|
|
|
|
|
|
|
|
// - If "Browse All" was used, the second prompt contains ALL selections
|
|
|
|
|
|
|
|
// - Otherwise, combine recommended + additional
|
|
|
|
|
|
|
|
const allSelectedIdes = wantsBrowseAll ? selectedAdditionalOrAll : [...filteredRecommended, ...selectedAdditionalOrAll];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
// STEP 3: Confirm if no tools selected
|
|
|
|
// STEP 3: Confirm if no tools selected
|
|
|
|
@ -547,7 +482,6 @@ class UI {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Display selected tools
|
|
|
|
// Display selected tools
|
|
|
|
const allTools = [...preferredIdes, ...otherIdes];
|
|
|
|
|
|
|
|
this.displaySelectedTools(allSelectedIdes, preferredIdes, allTools);
|
|
|
|
this.displaySelectedTools(allSelectedIdes, preferredIdes, allTools);
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
return {
|
|
|
|
@ -604,9 +538,23 @@ class UI {
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* Get confirmed directory from user
|
|
|
|
* Get confirmed directory from user
|
|
|
|
|
|
|
|
* @param {Object} options - CLI options object (may contain directory property)
|
|
|
|
* @returns {string} Confirmed directory path
|
|
|
|
* @returns {string} Confirmed directory path
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
async getConfirmedDirectory() {
|
|
|
|
async getConfirmedDirectory(options = {}) {
|
|
|
|
|
|
|
|
// If directory provided via CLI, validate and return it
|
|
|
|
|
|
|
|
if (options.directory) {
|
|
|
|
|
|
|
|
const expandedPath = this.expandUserPath(options.directory);
|
|
|
|
|
|
|
|
const validationError = this.validateDirectorySync(expandedPath);
|
|
|
|
|
|
|
|
if (validationError) {
|
|
|
|
|
|
|
|
throw new Error(`Invalid directory: ${validationError}`);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
await this.displayDirectoryInfo(expandedPath);
|
|
|
|
|
|
|
|
// Skip confirmation for CLI-provided directories
|
|
|
|
|
|
|
|
return expandedPath;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Existing interactive prompt logic
|
|
|
|
let confirmedDirectory = null;
|
|
|
|
let confirmedDirectory = null;
|
|
|
|
while (!confirmedDirectory) {
|
|
|
|
while (!confirmedDirectory) {
|
|
|
|
const directoryAnswer = await this.promptForDirectory();
|
|
|
|
const directoryAnswer = await this.promptForDirectory();
|
|
|
|
|