feat(cli): restore IDE grouping using groupMultiselect
Replace flat multiselect with native @clack/prompts groupMultiselect component to restore visual grouping of IDE/tool options: - "Previously Configured" - pre-selected IDEs from existing install - "Recommended Tools" - starred preferred options - "Additional Tools" - other available options This restores the grouped UX that was lost during the Inquirer.js to @clack/prompts migration. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
ae3dc2ce01
commit
1beaa0118b
|
|
@ -166,6 +166,30 @@ async function multiselect(options) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Grouped multi-select prompt for categorized options
|
||||||
|
* @param {Object} options - Prompt options
|
||||||
|
* @param {string} options.message - The question to ask
|
||||||
|
* @param {Object} options.options - Object mapping group names to arrays of choices
|
||||||
|
* @param {Array} [options.initialValues] - Array of initially selected values
|
||||||
|
* @param {boolean} [options.required=false] - Whether at least one must be selected
|
||||||
|
* @param {boolean} [options.selectableGroups=false] - Whether groups can be selected as a whole
|
||||||
|
* @returns {Promise<Array>} Array of selected values
|
||||||
|
*/
|
||||||
|
async function groupMultiselect(options) {
|
||||||
|
const clack = await getClack();
|
||||||
|
|
||||||
|
const result = await clack.groupMultiselect({
|
||||||
|
message: options.message,
|
||||||
|
options: options.options,
|
||||||
|
initialValues: options.initialValues,
|
||||||
|
required: options.required || false,
|
||||||
|
});
|
||||||
|
|
||||||
|
await handleCancel(result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Confirm prompt (replaces Inquirer 'confirm' type)
|
* Confirm prompt (replaces Inquirer 'confirm' type)
|
||||||
* @param {Object} options - Prompt options
|
* @param {Object} options - Prompt options
|
||||||
|
|
@ -397,6 +421,7 @@ module.exports = {
|
||||||
spinner,
|
spinner,
|
||||||
select,
|
select,
|
||||||
multiselect,
|
multiselect,
|
||||||
|
groupMultiselect,
|
||||||
confirm,
|
confirm,
|
||||||
text,
|
text,
|
||||||
password,
|
password,
|
||||||
|
|
|
||||||
|
|
@ -442,13 +442,14 @@ class UI {
|
||||||
const preferredIdes = ideManager.getPreferredIdes();
|
const preferredIdes = ideManager.getPreferredIdes();
|
||||||
const otherIdes = ideManager.getOtherIdes();
|
const otherIdes = ideManager.getOtherIdes();
|
||||||
|
|
||||||
// Build IDE choices array with separators
|
// Build grouped options object for groupMultiselect
|
||||||
const ideChoices = [];
|
const groupedOptions = {};
|
||||||
const processedIdes = new Set();
|
const processedIdes = new Set();
|
||||||
|
const initialValues = [];
|
||||||
|
|
||||||
// First, add previously configured IDEs at the top, marked with ✅
|
// First, add previously configured IDEs at the top, marked with ✅
|
||||||
if (configuredIdes.length > 0) {
|
if (configuredIdes.length > 0) {
|
||||||
ideChoices.push(new choiceUtils.Separator('── Previously Configured ──'));
|
const configuredGroup = [];
|
||||||
for (const ideValue of configuredIdes) {
|
for (const ideValue of configuredIdes) {
|
||||||
// Skip empty or invalid IDE values
|
// Skip empty or invalid IDE values
|
||||||
if (!ideValue || typeof ideValue !== 'string') {
|
if (!ideValue || typeof ideValue !== 'string') {
|
||||||
|
|
@ -461,44 +462,41 @@ class UI {
|
||||||
const ide = preferredIde || otherIde;
|
const ide = preferredIde || otherIde;
|
||||||
|
|
||||||
if (ide) {
|
if (ide) {
|
||||||
ideChoices.push({
|
configuredGroup.push({
|
||||||
name: `${ide.name} ✅`,
|
label: `${ide.name} ✅`,
|
||||||
value: ide.value,
|
value: ide.value,
|
||||||
checked: true, // Previously configured IDEs are checked by default
|
|
||||||
});
|
});
|
||||||
processedIdes.add(ide.value);
|
processedIdes.add(ide.value);
|
||||||
|
initialValues.push(ide.value); // Pre-select configured IDEs
|
||||||
} else {
|
} else {
|
||||||
// Warn about unrecognized IDE (but don't fail)
|
// Warn about unrecognized IDE (but don't fail)
|
||||||
console.log(chalk.yellow(`⚠️ Previously configured IDE '${ideValue}' is no longer available`));
|
console.log(chalk.yellow(`⚠️ Previously configured IDE '${ideValue}' is no longer available`));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (configuredGroup.length > 0) {
|
||||||
|
groupedOptions['Previously Configured'] = configuredGroup;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add preferred tools (excluding already processed)
|
// Add preferred tools (excluding already processed)
|
||||||
const remainingPreferred = preferredIdes.filter((ide) => !processedIdes.has(ide.value));
|
const remainingPreferred = preferredIdes.filter((ide) => !processedIdes.has(ide.value));
|
||||||
if (remainingPreferred.length > 0) {
|
if (remainingPreferred.length > 0) {
|
||||||
ideChoices.push(new choiceUtils.Separator('── Recommended Tools ──'));
|
groupedOptions['Recommended Tools'] = remainingPreferred.map((ide) => {
|
||||||
for (const ide of remainingPreferred) {
|
|
||||||
ideChoices.push({
|
|
||||||
name: `${ide.name} ⭐`,
|
|
||||||
value: ide.value,
|
|
||||||
checked: false,
|
|
||||||
});
|
|
||||||
processedIdes.add(ide.value);
|
processedIdes.add(ide.value);
|
||||||
}
|
return {
|
||||||
|
label: `${ide.name} ⭐`,
|
||||||
|
value: ide.value,
|
||||||
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add other tools (excluding already processed)
|
// Add other tools (excluding already processed)
|
||||||
const remainingOther = otherIdes.filter((ide) => !processedIdes.has(ide.value));
|
const remainingOther = otherIdes.filter((ide) => !processedIdes.has(ide.value));
|
||||||
if (remainingOther.length > 0) {
|
if (remainingOther.length > 0) {
|
||||||
ideChoices.push(new choiceUtils.Separator('── Additional Tools ──'));
|
groupedOptions['Additional Tools'] = remainingOther.map((ide) => ({
|
||||||
for (const ide of remainingOther) {
|
label: ide.name,
|
||||||
ideChoices.push({
|
value: ide.value,
|
||||||
name: ide.name,
|
}));
|
||||||
value: ide.value,
|
|
||||||
checked: false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let selectedIdes = [];
|
let selectedIdes = [];
|
||||||
|
|
@ -506,9 +504,10 @@ class UI {
|
||||||
|
|
||||||
// Loop until user selects at least one tool OR explicitly confirms no tools
|
// Loop until user selects at least one tool OR explicitly confirms no tools
|
||||||
while (!userConfirmedNoTools) {
|
while (!userConfirmedNoTools) {
|
||||||
selectedIdes = await prompts.multiselect({
|
selectedIdes = await prompts.groupMultiselect({
|
||||||
message: `Select tools to configure ${chalk.dim('(↑/↓ navigate, SPACE select, ENTER confirm)')}:`,
|
message: `Select tools to configure ${chalk.dim('(↑/↓ navigate, SPACE select, ENTER confirm)')}:`,
|
||||||
choices: ideChoices,
|
options: groupedOptions,
|
||||||
|
initialValues: initialValues.length > 0 ? initialValues : undefined,
|
||||||
required: false,
|
required: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue