UNPKG

@elsikora/commitizen-plugin-commitlint-ai

Version:
317 lines (314 loc) 12.4 kB
import chalk from 'chalk'; import ora from 'ora'; import prompts from 'prompts'; /** * Implementation of the CLI interface service using the prompts library. * Provides methods for interacting with the user through the command line. */ class PromptsCliInterface { /** Reference to the active spinner instance */ // @ts-ignore spinner; /** * Initializes a new instance of the PromptsCliInterface. * Sets up the spinner for providing visual feedback during operations. */ constructor() { this.spinner = ora(); } /** * Clears the console screen. */ clear() { process.stdout.write("\u001Bc"); } /** * Displays a confirmation prompt to the user. * @param {string} message - The message to display to the user * @param {boolean} isConfirmedByDefault - The default value for the confirmation, defaults to false * @returns {Promise<boolean>} Promise that resolves to the user's response (true for confirmed, false for declined) */ async confirm(message, isConfirmedByDefault = false) { try { const response = await prompts({ active: "Yes", inactive: "No", // eslint-disable-next-line @elsikora/typescript/naming-convention initial: isConfirmedByDefault, message, name: "value", type: "toggle", }); if (response.value === undefined) { this.error("Operation cancelled by user"); process.exit(0); } return response.value; } catch { this.error("Operation cancelled by user"); process.exit(0); } } /** * Displays an error message to the user. * @param {string} message - The error message to display */ error(message) { process.stderr.write(`${chalk.red(message)}\n`); } /** * Displays a grouped multi-select prompt to the user. * @param {string} message - The message to display to the user * @param {Record<string, Array<ICliInterfaceServiceSelectOptions>>} options - Record of groups and their options * @param {boolean} isRequired - Whether a selection is required, defaults to false * @param {Array<string>} initialValues - Initial selected values * @returns {Promise<Array<T>>} Promise that resolves to an array of selected values * @template T - The type of the selected values */ async groupMultiselect(message, options, isRequired = false, initialValues) { // Convert options to a flat array with group prefixes // eslint-disable-next-line @elsikora/typescript/naming-convention const choices = []; for (const [group, groupOptions] of Object.entries(options)) { for (const opt of groupOptions) { choices.push({ // eslint-disable-next-line @elsikora/typescript/naming-convention selected: initialValues?.includes(opt.value) ?? false, title: `${group}: ${opt.label}`, value: opt.value, }); } } try { const response = await prompts({ choices, // eslint-disable-next-line @elsikora/typescript/naming-convention instructions: false, message: `${message} (space to select)`, min: isRequired ? 1 : undefined, name: "values", type: "multiselect", }); if (response.values === undefined) { this.error("Operation cancelled by user"); process.exit(0); } return response.values; } catch { this.error("Operation cancelled by user"); process.exit(0); } } /** * Handles and displays an error message with additional error details. * @param {string} message - The error message to display * @param {unknown} error - The error object or details */ handleError(message, error) { process.stderr.write(`${chalk.red(message)}\n`); process.stderr.write(`${String(error)}\n`); } /** * Displays an informational message to the user. * @param {string} message - The info message to display */ info(message) { process.stdout.write(`${chalk.blue(message)}\n`); } /** * Displays a standard message to the user. * @param {string} message - The message to display */ log(message) { process.stdout.write(`${message}\n`); } /** * Displays a multi-select prompt to the user. * @param {string} message - The message to display to the user * @param {Array<ICliInterfaceServiceSelectOptions>} options - Array of options to select from * @param {boolean} isRequired - Whether a selection is required, defaults to false * @param {Array<string>} initialValues - Initial selected values * @returns {Promise<Array<T>>} Promise that resolves to an array of selected values * @template T - The type of the selected values */ async multiselect(message, options, isRequired = false, initialValues) { // eslint-disable-next-line @elsikora/typescript/naming-convention const choices = options.map((opt) => ({ // eslint-disable-next-line @elsikora/typescript/naming-convention selected: initialValues?.includes(opt.value) ?? false, title: opt.label, value: opt.value, })); try { const response = await prompts({ choices, // eslint-disable-next-line @elsikora/typescript/naming-convention instructions: false, message: `${message} (space to select)`, min: isRequired ? 1 : undefined, name: "values", type: "multiselect", }); if (response.values === undefined) { this.error("Operation cancelled by user"); process.exit(0); } return response.values; } catch { this.error("Operation cancelled by user"); process.exit(0); } } /** * Displays a note to the user with a title and message. * @param {string} title - The title of the note * @param {string} message - The message content of the note */ note(title, message) { const lines = message.split("\n"); // eslint-disable-next-line @elsikora/typescript/no-magic-numbers const width = Math.max(title.length, ...lines.map((line) => line.length)) + 4; // Add padding const top = `┌${"─".repeat(width)}┐`; const bottom = `└${"─".repeat(width)}┘`; // Create middle lines with padding // eslint-disable-next-line @elsikora/typescript/no-magic-numbers const paddedTitle = ` ${title.padEnd(width - 2)} `; // eslint-disable-next-line @elsikora/typescript/no-magic-numbers const paddedLines = lines.map((line) => ` ${line.padEnd(width - 2)} `); // Log the note box with styling process.stdout.write(`${chalk.dim(top)}\n`); process.stdout.write(`${chalk.dim("│") + chalk.bold(paddedTitle) + chalk.dim("│")}\n`); if (lines.length > 0) { // Add a separator line const separator = `├${"─".repeat(width)}┤`; process.stdout.write(`${chalk.dim(separator)}\n`); // Add message content for (const line of paddedLines) { process.stdout.write(`${chalk.dim("│") + chalk.dim(line) + chalk.dim("│")}\n`); } } process.stdout.write(`${chalk.dim(bottom)}\n`); } /** * Displays a single select prompt to the user. * @param {string} message - The message to display to the user * @param {Array<ICliInterfaceServiceSelectOptions>} options - Array of options to select from * @param {string} initialValue - Initial selected value * @returns {Promise<T>} Promise that resolves to the selected value * @template T - The type of the selected value */ async select(message, options, initialValue) { const choices = options.map((opt) => ({ title: opt.label, value: opt.value, })); const initialIndex = initialValue ? choices.findIndex((choice) => choice.value === initialValue) : undefined; try { const response = await prompts({ choices, initial: initialIndex === -1 ? 0 : initialIndex, message, name: "value", type: "select", }); if (response.value === undefined) { this.error("Operation cancelled by user"); process.exit(0); } return response.value; } catch { this.error("Operation cancelled by user"); process.exit(0); } } /** * Starts a spinner with the specified message. * Stops any existing spinner first. * @param {string} message - The message to display while the spinner is active */ startSpinner(message) { this.spinner.stop(); this.spinner = ora(message).start(); } /** * Stops the current spinner with an optional completion message. * @param {string} message - Optional message to display when the spinner stops */ stopSpinner(message) { this.spinner.stop(); if (message) { process.stdout.write(`${message}\n`); } } /** * Displays a success message to the user. * @param {string} message - The success message to display */ success(message) { process.stdout.write(`${chalk.green(message)}\n`); } /** * Displays a text input prompt to the user. * @param {string} message - The message to display to the user * @param {string} _placeholder - Optional placeholder text for the input field (unused) * @param {string} initialValue - Optional initial value for the input field * @param {(value: string) => Error | string | undefined} validate - Optional validation function for the input * @returns {Promise<string>} Promise that resolves to the user's input text */ async text(message, _placeholder, initialValue, validate) { // Convert the validate function to match prompts' expected format const promptsValidate = validate ? // eslint-disable-next-line @elsikora/typescript/explicit-function-return-type (value) => { const result = validate(value); if (result === undefined) return true; if (typeof result === "string") return result; if (result instanceof Error) return result.message; return "Invalid input"; } : undefined; try { const response = await prompts({ initial: initialValue, message, name: "value", type: "text", validate: promptsValidate, }); if (response.value === undefined) { this.error("Operation cancelled by user"); process.exit(0); } return response.value; } catch { this.error("Operation cancelled by user"); process.exit(0); } } /** * Update the spinner message without stopping it. * @param {string} message - The new message to display */ updateSpinner(message) { if (this.spinner?.isSpinning) { this.spinner.text = message; } } /** * Displays a warning message to the user. * @param {string} message - The warning message to display */ warn(message) { process.stdout.write(`${chalk.yellow(message)}\n`); } } export { PromptsCliInterface }; //# sourceMappingURL=prompts-cli-interface.service.js.map