feat: Update @clack/prompts to v1.0.0 and Add autocompleteMultiselect prompt
This commit is contained in:
parent
323cd75efd
commit
25ad95327c
|
|
@ -9,7 +9,8 @@
|
||||||
"version": "6.0.0-Beta.5",
|
"version": "6.0.0-Beta.5",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@clack/prompts": "^0.11.0",
|
"@clack/core": "^1.0.0",
|
||||||
|
"@clack/prompts": "^1.0.0",
|
||||||
"@kayvan/markdown-tree-parser": "^1.6.1",
|
"@kayvan/markdown-tree-parser": "^1.6.1",
|
||||||
"boxen": "^5.1.2",
|
"boxen": "^5.1.2",
|
||||||
"chalk": "^4.1.2",
|
"chalk": "^4.1.2",
|
||||||
|
|
@ -22,6 +23,7 @@
|
||||||
"ignore": "^7.0.5",
|
"ignore": "^7.0.5",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"ora": "^5.4.1",
|
"ora": "^5.4.1",
|
||||||
|
"picocolors": "^1.1.1",
|
||||||
"semver": "^7.6.3",
|
"semver": "^7.6.3",
|
||||||
"wrap-ansi": "^7.0.0",
|
"wrap-ansi": "^7.0.0",
|
||||||
"xml2js": "^0.6.2",
|
"xml2js": "^0.6.2",
|
||||||
|
|
@ -756,9 +758,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@clack/core": {
|
"node_modules/@clack/core": {
|
||||||
"version": "0.5.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@clack/core/-/core-0.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@clack/core/-/core-1.0.0.tgz",
|
||||||
"integrity": "sha512-p3y0FIOwaYRUPRcMO7+dlmLh8PSRcrjuTndsiA0WAFbWES0mLZlrjVoBRZ9DzkPFJZG6KGkJmoEAY0ZcVWTkow==",
|
"integrity": "sha512-Orf9Ltr5NeiEuVJS8Rk2XTw3IxNC2Bic3ash7GgYeA8LJ/zmSNpSQ/m5UAhe03lA6KFgklzZ5KTHs4OAMA/SAQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"picocolors": "^1.0.0",
|
"picocolors": "^1.0.0",
|
||||||
|
|
@ -766,12 +768,12 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@clack/prompts": {
|
"node_modules/@clack/prompts": {
|
||||||
"version": "0.11.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-0.11.0.tgz",
|
"resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-1.0.0.tgz",
|
||||||
"integrity": "sha512-pMN5FcrEw9hUkZA4f+zLlzivQSeQf5dRGJjSUbvVYDLvpKCdQx5OaknvKzgbtXOizhP+SJJJjqEbOe55uKKfAw==",
|
"integrity": "sha512-rWPXg9UaCFqErJVQ+MecOaWsozjaxol4yjnmYcGNipAWzdaWa2x+VJmKfGq7L0APwBohQOYdHC+9RO4qRXej+A==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@clack/core": "0.5.0",
|
"@clack/core": "1.0.0",
|
||||||
"picocolors": "^1.0.0",
|
"picocolors": "^1.0.0",
|
||||||
"sisteransi": "^1.0.5"
|
"sisteransi": "^1.0.5"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,8 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@clack/prompts": "^0.11.0",
|
"@clack/core": "^1.0.0",
|
||||||
|
"@clack/prompts": "^1.0.0",
|
||||||
"@kayvan/markdown-tree-parser": "^1.6.1",
|
"@kayvan/markdown-tree-parser": "^1.6.1",
|
||||||
"boxen": "^5.1.2",
|
"boxen": "^5.1.2",
|
||||||
"chalk": "^4.1.2",
|
"chalk": "^4.1.2",
|
||||||
|
|
@ -81,6 +82,7 @@
|
||||||
"ignore": "^7.0.5",
|
"ignore": "^7.0.5",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"ora": "^5.4.1",
|
"ora": "^5.4.1",
|
||||||
|
"picocolors": "^1.1.1",
|
||||||
"semver": "^7.6.3",
|
"semver": "^7.6.3",
|
||||||
"wrap-ansi": "^7.0.0",
|
"wrap-ansi": "^7.0.0",
|
||||||
"xml2js": "^0.6.2",
|
"xml2js": "^0.6.2",
|
||||||
|
|
|
||||||
|
|
@ -586,7 +586,11 @@ class ConfigCollector {
|
||||||
console.log();
|
console.log();
|
||||||
console.log(chalk.cyan('?') + ' ' + chalk.magenta(moduleDisplayName));
|
console.log(chalk.cyan('?') + ' ' + chalk.magenta(moduleDisplayName));
|
||||||
let customize = true;
|
let customize = true;
|
||||||
if (moduleName !== 'core') {
|
if (moduleName === 'core') {
|
||||||
|
// Core module: no confirm prompt, so add spacing manually to match visual style
|
||||||
|
console.log(chalk.gray('│'));
|
||||||
|
} else {
|
||||||
|
// Non-core modules: show "Accept Defaults?" confirm prompt (clack adds spacing)
|
||||||
const customizeAnswer = await prompts.prompt([
|
const customizeAnswer = await prompts.prompt([
|
||||||
{
|
{
|
||||||
type: 'confirm',
|
type: 'confirm',
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
let _clack = null;
|
let _clack = null;
|
||||||
|
let _clackCore = null;
|
||||||
|
let _picocolors = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lazy-load @clack/prompts (ESM module)
|
* Lazy-load @clack/prompts (ESM module)
|
||||||
|
|
@ -20,6 +22,28 @@ async function getClack() {
|
||||||
return _clack;
|
return _clack;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lazy-load @clack/core (ESM module)
|
||||||
|
* @returns {Promise<Object>} The clack core module
|
||||||
|
*/
|
||||||
|
async function getClackCore() {
|
||||||
|
if (!_clackCore) {
|
||||||
|
_clackCore = await import('@clack/core');
|
||||||
|
}
|
||||||
|
return _clackCore;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lazy-load picocolors
|
||||||
|
* @returns {Promise<Object>} The picocolors module
|
||||||
|
*/
|
||||||
|
async function getPicocolors() {
|
||||||
|
if (!_picocolors) {
|
||||||
|
_picocolors = (await import('picocolors')).default;
|
||||||
|
}
|
||||||
|
return _picocolors;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle user cancellation gracefully
|
* Handle user cancellation gracefully
|
||||||
* @param {any} value - The value to check
|
* @param {any} value - The value to check
|
||||||
|
|
@ -191,6 +215,35 @@ async function groupMultiselect(options) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Autocomplete multi-select prompt with type-ahead filtering
|
||||||
|
* @param {Object} options - Prompt options
|
||||||
|
* @param {string} options.message - The question to ask
|
||||||
|
* @param {Array} options.options - Array of choices [{label, value, hint?}]
|
||||||
|
* @param {string} [options.placeholder] - Placeholder text for search input
|
||||||
|
* @param {Array} [options.initialValues] - Array of initially selected values
|
||||||
|
* @param {boolean} [options.required=false] - Whether at least one must be selected
|
||||||
|
* @param {number} [options.maxItems=5] - Maximum visible items in scrollable list
|
||||||
|
* @param {Function} [options.filter] - Custom filter function (search, option) => boolean
|
||||||
|
* @returns {Promise<Array>} Array of selected values
|
||||||
|
*/
|
||||||
|
async function autocompleteMultiselect(options) {
|
||||||
|
const clack = await getClack();
|
||||||
|
|
||||||
|
const result = await clack.autocompleteMultiselect({
|
||||||
|
message: options.message,
|
||||||
|
options: options.options,
|
||||||
|
placeholder: options.placeholder || 'Type to search...',
|
||||||
|
initialValues: options.initialValues,
|
||||||
|
required: options.required || false,
|
||||||
|
maxItems: options.maxItems || 5,
|
||||||
|
filter: options.filter,
|
||||||
|
});
|
||||||
|
|
||||||
|
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
|
||||||
|
|
@ -211,7 +264,12 @@ async function confirm(options) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Text input prompt (replaces Inquirer 'input' type)
|
* Text input prompt with Tab-to-fill-placeholder support (replaces Inquirer 'input' type)
|
||||||
|
*
|
||||||
|
* This custom implementation restores the Tab-to-fill-placeholder behavior that was
|
||||||
|
* intentionally removed in @clack/prompts v1.0.0 (placeholder became purely visual).
|
||||||
|
* Uses @clack/core's TextPrompt primitive with custom key handling.
|
||||||
|
*
|
||||||
* @param {Object} options - Prompt options
|
* @param {Object} options - Prompt options
|
||||||
* @param {string} options.message - The question to ask
|
* @param {string} options.message - The question to ask
|
||||||
* @param {string} [options.default] - Default value
|
* @param {string} [options.default] - Default value
|
||||||
|
|
@ -220,20 +278,64 @@ async function confirm(options) {
|
||||||
* @returns {Promise<string>} User's input
|
* @returns {Promise<string>} User's input
|
||||||
*/
|
*/
|
||||||
async function text(options) {
|
async function text(options) {
|
||||||
const clack = await getClack();
|
const core = await getClackCore();
|
||||||
|
const color = await getPicocolors();
|
||||||
|
|
||||||
// Use default as placeholder if placeholder not explicitly provided
|
// Use default as placeholder if placeholder not explicitly provided
|
||||||
// This shows the default value as grayed-out hint text
|
// This shows the default value as grayed-out hint text
|
||||||
const placeholder = options.placeholder === undefined ? options.default : options.placeholder;
|
const placeholder = options.placeholder === undefined ? options.default : options.placeholder;
|
||||||
|
const defaultValue = options.default;
|
||||||
|
|
||||||
const result = await clack.text({
|
const prompt = new core.TextPrompt({
|
||||||
message: options.message,
|
defaultValue,
|
||||||
defaultValue: options.default,
|
|
||||||
placeholder: typeof placeholder === 'string' ? placeholder : undefined,
|
|
||||||
validate: options.validate,
|
validate: options.validate,
|
||||||
|
render() {
|
||||||
|
const title = `${color.gray('◆')} ${options.message}`;
|
||||||
|
|
||||||
|
// Show placeholder as dim text when input is empty
|
||||||
|
let valueDisplay;
|
||||||
|
if (this.state === 'error') {
|
||||||
|
valueDisplay = color.yellow(this.userInputWithCursor);
|
||||||
|
} else if (this.userInput) {
|
||||||
|
valueDisplay = this.userInputWithCursor;
|
||||||
|
} else if (placeholder) {
|
||||||
|
// Show placeholder with cursor indicator when empty
|
||||||
|
valueDisplay = `${color.inverse(color.hidden('_'))}${color.dim(placeholder)}`;
|
||||||
|
} else {
|
||||||
|
valueDisplay = color.inverse(color.hidden('_'));
|
||||||
|
}
|
||||||
|
|
||||||
|
const bar = color.gray('│');
|
||||||
|
|
||||||
|
// Handle different states
|
||||||
|
if (this.state === 'submit') {
|
||||||
|
return `${color.gray('◇')} ${options.message}\n${bar} ${color.dim(this.value || defaultValue || '')}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.state === 'cancel') {
|
||||||
|
return `${color.gray('◇')} ${options.message}\n${bar} ${color.strikethrough(color.dim(this.userInput || ''))}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.state === 'error') {
|
||||||
|
return `${color.yellow('▲')} ${options.message}\n${bar} ${valueDisplay}\n${color.yellow('│')} ${color.yellow(this.error)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${title}\n${bar} ${valueDisplay}\n${bar}`;
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Add Tab key handler to fill placeholder into input
|
||||||
|
prompt.on('key', (char) => {
|
||||||
|
if (char === '\t' && placeholder && !prompt.userInput) {
|
||||||
|
// Use _setUserInput with write=true to populate the readline and update internal state
|
||||||
|
prompt._setUserInput(placeholder, true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await prompt.prompt();
|
||||||
await handleCancel(result);
|
await handleCancel(result);
|
||||||
|
|
||||||
|
// TextPrompt's finalize handler already applies defaultValue for empty input
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -423,6 +525,7 @@ module.exports = {
|
||||||
select,
|
select,
|
||||||
multiselect,
|
multiselect,
|
||||||
groupMultiselect,
|
groupMultiselect,
|
||||||
|
autocompleteMultiselect,
|
||||||
confirm,
|
confirm,
|
||||||
text,
|
text,
|
||||||
password,
|
password,
|
||||||
|
|
|
||||||
|
|
@ -344,6 +344,9 @@ class UI {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prompt for tool/IDE selection (called after module configuration)
|
* Prompt for tool/IDE selection (called after module configuration)
|
||||||
|
* Uses a split prompt approach:
|
||||||
|
* 1. Recommended tools - standard multiselect for 3 preferred tools
|
||||||
|
* 2. Additional tools - autocompleteMultiselect with search capability
|
||||||
* @param {string} projectDir - Project directory to check for existing IDEs
|
* @param {string} projectDir - Project directory to check for existing IDEs
|
||||||
* @returns {Object} Tool configuration
|
* @returns {Object} Tool configuration
|
||||||
*/
|
*/
|
||||||
|
|
@ -366,95 +369,126 @@ class UI {
|
||||||
const preferredIdes = ideManager.getPreferredIdes();
|
const preferredIdes = ideManager.getPreferredIdes();
|
||||||
const otherIdes = ideManager.getOtherIdes();
|
const otherIdes = ideManager.getOtherIdes();
|
||||||
|
|
||||||
// Build grouped options object for groupMultiselect
|
// Determine which configured IDEs are in "preferred" vs "other" categories
|
||||||
const groupedOptions = {};
|
const configuredPreferred = configuredIdes.filter((id) => preferredIdes.some((ide) => ide.value === id));
|
||||||
const processedIdes = new Set();
|
const configuredOther = configuredIdes.filter((id) => otherIdes.some((ide) => ide.value === id));
|
||||||
const initialValues = [];
|
|
||||||
|
|
||||||
// First, add previously configured IDEs, marked with ✅
|
// Warn about previously configured tools that are no longer available
|
||||||
if (configuredIdes.length > 0) {
|
const allKnownValues = new Set([...preferredIdes, ...otherIdes].map((ide) => ide.value));
|
||||||
const configuredGroup = [];
|
const unknownTools = configuredIdes.filter((id) => id && typeof id === 'string' && !allKnownValues.has(id));
|
||||||
for (const ideValue of configuredIdes) {
|
if (unknownTools.length > 0) {
|
||||||
// Skip empty or invalid IDE values
|
console.log(chalk.yellow(`⚠️ Previously configured tools are no longer available: ${unknownTools.join(', ')}`));
|
||||||
if (!ideValue || typeof ideValue !== 'string') {
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the IDE in either preferred or other lists
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
const preferredIde = preferredIdes.find((ide) => ide.value === ideValue);
|
// STEP 1: Recommended Tools (multiselect)
|
||||||
const otherIde = otherIdes.find((ide) => ide.value === ideValue);
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
const ide = preferredIde || otherIde;
|
const recommendedOptions = preferredIdes.map((ide) => {
|
||||||
|
const isConfigured = configuredPreferred.includes(ide.value);
|
||||||
if (ide) {
|
|
||||||
configuredGroup.push({
|
|
||||||
label: `${ide.name} ✅`,
|
|
||||||
value: ide.value,
|
|
||||||
});
|
|
||||||
processedIdes.add(ide.value);
|
|
||||||
initialValues.push(ide.value); // Pre-select configured IDEs
|
|
||||||
} else {
|
|
||||||
// Warn about unrecognized IDE (but don't fail)
|
|
||||||
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)
|
|
||||||
const remainingPreferred = preferredIdes.filter((ide) => !processedIdes.has(ide.value));
|
|
||||||
if (remainingPreferred.length > 0) {
|
|
||||||
groupedOptions['Recommended Tools'] = remainingPreferred.map((ide) => {
|
|
||||||
processedIdes.add(ide.value);
|
|
||||||
return {
|
return {
|
||||||
label: `${ide.name} ⭐`,
|
label: isConfigured ? `${ide.name} ⭐ ✅` : `${ide.name} ⭐`,
|
||||||
value: ide.value,
|
value: ide.value,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
// Add other tools (excluding already processed)
|
// Add "__NONE__" option at the end
|
||||||
const remainingOther = otherIdes.filter((ide) => !processedIdes.has(ide.value));
|
recommendedOptions.push({
|
||||||
if (remainingOther.length > 0) {
|
|
||||||
groupedOptions['Additional Tools'] = remainingOther.map((ide) => ({
|
|
||||||
label: ide.name,
|
|
||||||
value: ide.value,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add standalone "None" option at the end
|
|
||||||
groupedOptions[' '] = [
|
|
||||||
{
|
|
||||||
label: '⚠ None - I am not installing any tools',
|
label: '⚠ None - I am not installing any tools',
|
||||||
value: '__NONE__',
|
value: '__NONE__',
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
let selectedIdes = [];
|
|
||||||
|
|
||||||
selectedIdes = await prompts.groupMultiselect({
|
|
||||||
message: `Select tools to configure ${chalk.dim('(↑/↓ navigates, SPACE toggles, ENTER to confirm)')}:`,
|
|
||||||
options: groupedOptions,
|
|
||||||
initialValues: initialValues.length > 0 ? initialValues : undefined,
|
|
||||||
required: true,
|
|
||||||
selectableGroups: false,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// If user selected both "__NONE__" and other tools, honor the "None" choice
|
// Pre-select previously configured preferred tools
|
||||||
if (selectedIdes && selectedIdes.includes('__NONE__') && selectedIdes.length > 1) {
|
const recommendedInitialValues = configuredPreferred.length > 0 ? configuredPreferred : undefined;
|
||||||
|
|
||||||
|
const recommendedSelected = await prompts.multiselect({
|
||||||
|
message: `Select recommended tools ${chalk.dim('(↑/↓ navigates, SPACE toggles, ENTER to confirm)')}:`,
|
||||||
|
options: recommendedOptions,
|
||||||
|
initialValues: recommendedInitialValues,
|
||||||
|
required: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle "__NONE__" selection
|
||||||
|
if (recommendedSelected && recommendedSelected.includes('__NONE__')) {
|
||||||
|
if (recommendedSelected.length > 1) {
|
||||||
console.log();
|
console.log();
|
||||||
console.log(chalk.yellow('⚠️ "None - I am not installing any tools" was selected, so no tools will be configured.'));
|
console.log(chalk.yellow('⚠️ "None - I am not installing any tools" was selected, so no tools will be configured.'));
|
||||||
console.log();
|
console.log();
|
||||||
selectedIdes = [];
|
}
|
||||||
} else if (selectedIdes && selectedIdes.includes('__NONE__')) {
|
return {
|
||||||
// Only "__NONE__" was selected
|
ides: [],
|
||||||
selectedIdes = [];
|
skipIde: true,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Filter out any special values from recommended selection
|
||||||
|
const selectedRecommended = (recommendedSelected || []).filter((v) => v !== '__NONE__');
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
// STEP 2: "Add more tools?" confirmation
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
// Auto-show additional tools prompt if user has configured "other" tools
|
||||||
|
// Otherwise, ask if they want to add more
|
||||||
|
let showAdditionalPrompt = configuredOther.length > 0;
|
||||||
|
|
||||||
|
if (!showAdditionalPrompt && otherIdes.length > 0) {
|
||||||
|
console.log('');
|
||||||
|
showAdditionalPrompt = await prompts.confirm({
|
||||||
|
message: 'Add more tools from the extended list?',
|
||||||
|
default: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let selectedAdditional = [];
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
// STEP 3: Additional Tools (autocompleteMultiselect with search)
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
if (showAdditionalPrompt && otherIdes.length > 0) {
|
||||||
|
// Build options for additional tools, excluding any already selected in recommended
|
||||||
|
const additionalOptions = otherIdes
|
||||||
|
.filter((ide) => !selectedRecommended.includes(ide.value))
|
||||||
|
.map((ide) => {
|
||||||
|
const isConfigured = configuredOther.includes(ide.value);
|
||||||
return {
|
return {
|
||||||
ides: selectedIdes || [],
|
label: isConfigured ? `${ide.name} ✅` : ide.name,
|
||||||
skipIde: !selectedIdes || selectedIdes.length === 0,
|
value: ide.value,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add "__SKIP__" option at the end
|
||||||
|
additionalOptions.push({
|
||||||
|
label: '⚠ Skip - Keep recommended selections only',
|
||||||
|
value: '__SKIP__',
|
||||||
|
});
|
||||||
|
|
||||||
|
// Pre-select previously configured other tools
|
||||||
|
const additionalInitialValues = configuredOther.length > 0 ? configuredOther : undefined;
|
||||||
|
|
||||||
|
console.log('');
|
||||||
|
const additionalSelected = await prompts.autocompleteMultiselect({
|
||||||
|
message: 'Select additional tools:',
|
||||||
|
options: additionalOptions,
|
||||||
|
initialValues: additionalInitialValues,
|
||||||
|
required: true,
|
||||||
|
maxItems: 6,
|
||||||
|
placeholder: 'Type to search...',
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle "__SKIP__" selection
|
||||||
|
if (additionalSelected && additionalSelected.includes('__SKIP__')) {
|
||||||
|
// User chose to skip - keep only recommended selections
|
||||||
|
selectedAdditional = [];
|
||||||
|
} else {
|
||||||
|
selectedAdditional = (additionalSelected || []).filter((v) => v !== '__SKIP__');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Combine selections
|
||||||
|
const allSelectedIdes = [...selectedRecommended, ...selectedAdditional];
|
||||||
|
|
||||||
|
return {
|
||||||
|
ides: allSelectedIdes,
|
||||||
|
skipIde: allSelectedIdes.length === 0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue