diff --git a/packages/prompts/src/autocomplete.ts b/packages/prompts/src/autocomplete.ts index e55b285f..de092097 100644 --- a/packages/prompts/src/autocomplete.ts +++ b/packages/prompts/src/autocomplete.ts @@ -1,14 +1,14 @@ import { AutocompletePrompt } from '@clack/core'; import color from 'picocolors'; import { + type CheckboxTheme, type CommonOptions, + extendStyle, + getThemeColor, + getThemePrefix, + type RadioTheme, S_BAR, S_BAR_END, - S_CHECKBOX_INACTIVE, - S_CHECKBOX_SELECTED, - S_RADIO_ACTIVE, - S_RADIO_INACTIVE, - symbol, } from './common.js'; import { limitOptions } from './limit-options.js'; import type { Option } from './select.js'; @@ -41,7 +41,7 @@ function getSelectedOptions(values: T[], options: Option[]): Option[] { return results; } -interface AutocompleteSharedOptions extends CommonOptions { +type AutocompleteSharedOptions = CommonOptions & { /** * The message to display to the user. */ @@ -62,9 +62,9 @@ interface AutocompleteSharedOptions extends CommonOptions { * Validates the value */ validate?: (value: Value | Value[] | undefined) => string | Error | undefined; -} +}; -export interface AutocompleteOptions extends AutocompleteSharedOptions { +export interface AutocompleteOptions extends AutocompleteSharedOptions { /** * The initial selected value. */ @@ -76,6 +76,7 @@ export interface AutocompleteOptions extends AutocompleteSharedOptions(opts: AutocompleteOptions) => { + const style = extendStyle(opts.theme); const prompt = new AutocompletePrompt({ options: opts.options, initialValue: opts.initialValue ? [opts.initialValue] : undefined, @@ -88,13 +89,17 @@ export const autocomplete = (opts: AutocompleteOptions) => { output: opts.output, validate: opts.validate, render() { + const themeColor = getThemeColor(this.state); + const themePrefix = getThemePrefix(this.state); + // Title and message display - const headings = [`${color.gray(S_BAR)}`, `${symbol(this.state)} ${opts.message}`]; + const headings = [`${color.gray(S_BAR)}`, `${style[themePrefix]} ${opts.message}`]; const userInput = this.userInput; const valueAsString = String(this.value ?? ''); const options = this.options; const placeholder = opts.placeholder; const showPlaceholder = valueAsString === '' && placeholder !== undefined; + const bar = style[themeColor](S_BAR); // Handle different states switch (this.state) { @@ -103,12 +108,12 @@ export const autocomplete = (opts: AutocompleteOptions) => { const selected = getSelectedOptions(this.selectedValues, options); const label = selected.length > 0 ? ` ${color.dim(selected.map(getLabel).join(', '))}` : ''; - return `${headings.join('\n')}\n${color.gray(S_BAR)}${label}`; + return `${headings.join('\n')}\n${bar}${label}`; } case 'cancel': { const userInputText = userInput ? ` ${color.strikethrough(color.dim(userInput))}` : ''; - return `${headings.join('\n')}\n${color.gray(S_BAR)}${userInputText}`; + return `${headings.join('\n')}\n${bar}${userInputText}`; } default: { @@ -132,15 +137,15 @@ export const autocomplete = (opts: AutocompleteOptions) => { // No matches message const noResults = this.filteredOptions.length === 0 && userInput - ? [`${color.cyan(S_BAR)} ${color.yellow('No matches found')}`] + ? [`${bar} ${style.colorError('No matches found')}`] : []; const validationError = - this.state === 'error' ? [`${color.yellow(S_BAR)} ${color.yellow(this.error)}`] : []; + this.state === 'error' ? [`${bar} ${style[themeColor](this.error)}`] : []; headings.push( - `${color.cyan(S_BAR)}`, - `${color.cyan(S_BAR)} ${color.dim('Search:')}${searchText}${matches}`, + `${bar}`, + `${bar} ${color.dim('Search:')}${searchText}${matches}`, ...noResults, ...validationError ); @@ -153,8 +158,8 @@ export const autocomplete = (opts: AutocompleteOptions) => { ]; const footers = [ - `${color.cyan(S_BAR)} ${color.dim(instructions.join(' • '))}`, - `${color.cyan(S_BAR_END)}`, + `${bar} ${color.dim(instructions.join(' • '))}`, + `${style[themeColor](S_BAR_END)}`, ]; // Render options with selection @@ -174,8 +179,8 @@ export const autocomplete = (opts: AutocompleteOptions) => { : ''; return active - ? `${color.green(S_RADIO_ACTIVE)} ${label}${hint}` - : `${color.dim(S_RADIO_INACTIVE)} ${color.dim(label)}${hint}`; + ? `${style.radioActive} ${label}${hint}` + : `${style.radioInactive} ${color.dim(label)}${hint}`; }, maxItems: opts.maxItems, output: opts.output, @@ -184,7 +189,7 @@ export const autocomplete = (opts: AutocompleteOptions) => { // Return the formatted prompt return [ ...headings, - ...displayOptions.map((option) => `${color.cyan(S_BAR)} ${option}`), + ...displayOptions.map((option) => `${bar} ${option}`), ...footers, ].join('\n'); } @@ -197,7 +202,8 @@ export const autocomplete = (opts: AutocompleteOptions) => { }; // Type definition for the autocompleteMultiselect component -export interface AutocompleteMultiSelectOptions extends AutocompleteSharedOptions { +export interface AutocompleteMultiSelectOptions + extends AutocompleteSharedOptions { /** * The initial selected values */ @@ -212,6 +218,7 @@ export interface AutocompleteMultiSelectOptions extends AutocompleteShare * Integrated autocomplete multiselect - combines type-ahead filtering with multiselect in one UI */ export const autocompleteMultiselect = (opts: AutocompleteMultiSelectOptions) => { + const style = extendStyle(opts.theme); const formatOption = ( option: Option, active: boolean, @@ -224,11 +231,13 @@ export const autocompleteMultiselect = (opts: AutocompleteMultiSelectOpti option.hint && focusedValue !== undefined && option.value === focusedValue ? color.dim(` (${option.hint})`) : ''; - const checkbox = isSelected ? color.green(S_CHECKBOX_SELECTED) : color.dim(S_CHECKBOX_INACTIVE); if (active) { + const checkbox = isSelected ? style.checkboxSelectedActive : style.checkboxUnselectedActive; return `${checkbox} ${label}${hint}`; } + + const checkbox = isSelected ? style.checkboxSelectedInactive : style.checkboxUnselectedInactive; return `${checkbox} ${color.dim(label)}`; }; @@ -250,8 +259,12 @@ export const autocompleteMultiselect = (opts: AutocompleteMultiSelectOpti input: opts.input, output: opts.output, render() { + const themeColor = getThemeColor(this.state); + const themePrefix = getThemePrefix(this.state); + // Title and symbol - const title = `${color.gray(S_BAR)}\n${symbol(this.state)} ${opts.message}\n`; + const title = `${color.gray(S_BAR)}\n${style[themePrefix]} ${opts.message}`; + const bar = style[themeColor](S_BAR); // Selection counter const userInput = this.userInput; @@ -276,10 +289,10 @@ export const autocompleteMultiselect = (opts: AutocompleteMultiSelectOpti // Render prompt state switch (this.state) { case 'submit': { - return `${title}${color.gray(S_BAR)} ${color.dim(`${this.selectedValues.length} items selected`)}`; + return `${title}\n${bar} ${color.dim(`${this.selectedValues.length} items selected`)}`; } case 'cancel': { - return `${title}${color.gray(S_BAR)} ${color.strikethrough(color.dim(userInput))}`; + return `${title}\n${bar} ${color.strikethrough(color.dim(userInput))}`; } default: { // Instructions @@ -293,11 +306,11 @@ export const autocompleteMultiselect = (opts: AutocompleteMultiSelectOpti // No results message const noResults = this.filteredOptions.length === 0 && userInput - ? [`${color.cyan(S_BAR)} ${color.yellow('No matches found')}`] + ? [`${bar} ${style.colorError('No matches found')}`] : []; const errorMessage = - this.state === 'error' ? [`${color.cyan(S_BAR)} ${color.yellow(this.error)}`] : []; + this.state === 'error' ? [`${bar} ${style[themeColor](this.error)}`] : []; // Get limited options for display const displayOptions = limitOptions({ @@ -312,12 +325,12 @@ export const autocompleteMultiselect = (opts: AutocompleteMultiSelectOpti // Build the prompt display return [ title, - `${color.cyan(S_BAR)} ${color.dim('Search:')} ${searchText}${matches}`, + `${bar} ${color.dim('Search:')} ${searchText}${matches}`, ...noResults, ...errorMessage, - ...displayOptions.map((option) => `${color.cyan(S_BAR)} ${option}`), - `${color.cyan(S_BAR)} ${color.dim(instructions.join(' • '))}`, - `${color.cyan(S_BAR_END)}`, + ...displayOptions.map((option) => `${bar} ${option}`), + `${bar} ${color.dim(instructions.join(' • '))}`, + `${style[themeColor](S_BAR_END)}`, ].join('\n'); } } diff --git a/packages/prompts/src/common.ts b/packages/prompts/src/common.ts index 2489a815..248346e2 100644 --- a/packages/prompts/src/common.ts +++ b/packages/prompts/src/common.ts @@ -39,36 +39,69 @@ export const S_SUCCESS = unicodeOr('◆', '*'); export const S_WARN = unicodeOr('▲', '!'); export const S_ERROR = unicodeOr('■', 'x'); -export const symbol = (state: State) => { - switch (state) { - case 'initial': - case 'active': - return color.cyan(S_STEP_ACTIVE); - case 'cancel': - return color.red(S_STEP_CANCEL); - case 'error': - return color.yellow(S_STEP_ERROR); - case 'submit': - return color.green(S_STEP_SUBMIT); - } -}; - -export const symbolBar = (state: State) => { - switch (state) { - case 'initial': - case 'active': - return color.cyan(S_BAR); - case 'cancel': - return color.red(S_BAR); - case 'error': - return color.yellow(S_BAR); - case 'submit': - return color.green(S_BAR); - } -}; +type ColorState = `color${Capitalize}`; +type PrefixState = `prefix${Capitalize}`; -export interface CommonOptions { +export type CommonOptions = { input?: Readable; output?: Writable; signal?: AbortSignal; +} & (TStyle extends object + ? { + theme?: { [K in ColorState]?: (str: string) => string } & { + [K in PrefixState]?: string; + } & TStyle; + } + : {}); + +export interface RadioTheme { + radioActive?: string; + radioInactive?: string; + radioDisabled?: string; } + +export interface CheckboxTheme { + checkboxSelectedActive?: string; + checkboxSelectedInactive?: string; + checkboxUnselectedActive?: string; + checkboxUnselectedInactive?: string; + checkboxDisabled?: string; +} + +const defaultStyle: CommonOptions['theme'] = { + colorInitial: color.cyan, + colorActive: color.cyan, + colorCancel: color.gray, + colorError: color.yellow, + colorSubmit: color.gray, + + prefixInitial: color.cyan(S_STEP_ACTIVE), + prefixActive: color.cyan(S_STEP_ACTIVE), + prefixCancel: color.red(S_STEP_CANCEL), + prefixError: color.yellow(S_STEP_ERROR), + prefixSubmit: color.green(S_STEP_SUBMIT), + + radioActive: color.green(S_RADIO_ACTIVE), + radioInactive: color.dim(S_RADIO_INACTIVE), + radioDisabled: color.gray(S_RADIO_INACTIVE), + + checkboxSelectedActive: color.green(S_CHECKBOX_SELECTED), + checkboxSelectedInactive: color.green(S_CHECKBOX_SELECTED), + checkboxUnselectedActive: color.cyan(S_CHECKBOX_ACTIVE), + checkboxUnselectedInactive: color.dim(S_CHECKBOX_INACTIVE), + checkboxDisabled: color.gray(S_CHECKBOX_INACTIVE), +}; + +type ExtendStyleType = ({ theme: {} } & CommonOptions)['theme']; + +export const extendStyle = (style?: ExtendStyleType) => { + return { + ...defaultStyle, + ...(style ?? {}), + } as Required>; +}; + +const capitalize = (str: string): string => str[0].toUpperCase() + str.substring(1); + +export const getThemeColor = (state: State) => `color${capitalize(state)}` as ColorState; +export const getThemePrefix = (state: State) => `prefix${capitalize(state)}` as PrefixState; diff --git a/packages/prompts/src/confirm.ts b/packages/prompts/src/confirm.ts index 4ee30acc..0b115f92 100644 --- a/packages/prompts/src/confirm.ts +++ b/packages/prompts/src/confirm.ts @@ -2,20 +2,22 @@ import { ConfirmPrompt } from '@clack/core'; import color from 'picocolors'; import { type CommonOptions, + extendStyle, + getThemeColor, + getThemePrefix, + type RadioTheme, S_BAR, S_BAR_END, - S_RADIO_ACTIVE, - S_RADIO_INACTIVE, - symbol, } from './common.js'; -export interface ConfirmOptions extends CommonOptions { +export interface ConfirmOptions extends CommonOptions { message: string; active?: string; inactive?: string; initialValue?: boolean; } export const confirm = (opts: ConfirmOptions) => { + const style = extendStyle(opts.theme); const active = opts.active ?? 'Yes'; const inactive = opts.inactive ?? 'No'; return new ConfirmPrompt({ @@ -26,26 +28,30 @@ export const confirm = (opts: ConfirmOptions) => { output: opts.output, initialValue: opts.initialValue ?? true, render() { - const title = `${color.gray(S_BAR)}\n${symbol(this.state)} ${opts.message}\n`; + const themeColor = getThemeColor(this.state); + const themePrefix = getThemePrefix(this.state); + + const bar = style[themeColor](S_BAR); + const barEnd = style[themeColor](S_BAR_END); + + const title = `${color.gray(S_BAR)}\n${style[themePrefix]} ${opts.message}\n`; const value = this.value ? active : inactive; switch (this.state) { case 'submit': - return `${title}${color.gray(S_BAR)} ${color.dim(value)}`; + return `${title}${bar} ${color.dim(value)}`; case 'cancel': - return `${title}${color.gray(S_BAR)} ${color.strikethrough( - color.dim(value) - )}\n${color.gray(S_BAR)}`; + return `${title}${bar} ${color.strikethrough(color.dim(value))}\n${bar}`; default: { - return `${title}${color.cyan(S_BAR)} ${ + return `${title}${bar} ${ this.value - ? `${color.green(S_RADIO_ACTIVE)} ${active}` - : `${color.dim(S_RADIO_INACTIVE)} ${color.dim(active)}` + ? `${style.radioActive} ${active}` + : `${style.radioInactive} ${color.dim(active)}` } ${color.dim('/')} ${ !this.value - ? `${color.green(S_RADIO_ACTIVE)} ${inactive}` - : `${color.dim(S_RADIO_INACTIVE)} ${color.dim(inactive)}` - }\n${color.cyan(S_BAR_END)}\n`; + ? `${style.radioActive} ${inactive}` + : `${style.radioInactive} ${color.dim(inactive)}` + }\n${barEnd}\n`; } } }, diff --git a/packages/prompts/src/group-multi-select.ts b/packages/prompts/src/group-multi-select.ts index 4464b074..dd17cc10 100644 --- a/packages/prompts/src/group-multi-select.ts +++ b/packages/prompts/src/group-multi-select.ts @@ -1,17 +1,17 @@ import { GroupMultiSelectPrompt } from '@clack/core'; import color from 'picocolors'; import { + type CheckboxTheme, type CommonOptions, + extendStyle, + getThemeColor, + getThemePrefix, S_BAR, S_BAR_END, - S_CHECKBOX_ACTIVE, - S_CHECKBOX_INACTIVE, - S_CHECKBOX_SELECTED, - symbol, } from './common.js'; import type { Option } from './select.js'; -export interface GroupMultiSelectOptions extends CommonOptions { +export interface GroupMultiSelectOptions extends CommonOptions { message: string; options: Record[]>; initialValues?: Value[]; @@ -21,6 +21,7 @@ export interface GroupMultiSelectOptions extends CommonOptions { groupSpacing?: number; } export const groupMultiselect = (opts: GroupMultiSelectOptions) => { + const style = extendStyle(opts.theme); const { selectableGroups = true, groupSpacing = 0 } = opts; const opt = ( option: Option & { group: string | boolean }, @@ -33,7 +34,8 @@ export const groupMultiselect = (opts: GroupMultiSelectOptions) => | 'group-active-selected' | 'submitted' | 'cancelled', - options: (Option & { group: string | boolean })[] = [] + options: (Option & { group: string | boolean })[] = [], + prefixBar: string ) => { const label = option.label ?? String(option.value); const isItem = typeof option.group === 'string'; @@ -42,23 +44,23 @@ export const groupMultiselect = (opts: GroupMultiSelectOptions) => const prefix = isItem ? (selectableGroups ? `${isLast ? S_BAR_END : S_BAR} ` : ' ') : ''; let spacingPrefix = ''; if (groupSpacing > 0 && !isItem) { - const spacingPrefixText = `\n${color.cyan(S_BAR)}`; + const spacingPrefixText = `\n${prefixBar}`; spacingPrefix = `${spacingPrefixText.repeat(groupSpacing - 1)}${spacingPrefixText} `; } if (state === 'active') { - return `${spacingPrefix}${color.dim(prefix)}${color.cyan(S_CHECKBOX_ACTIVE)} ${label}${ + return `${spacingPrefix}${color.dim(prefix)}${style.checkboxUnselectedActive} ${label}${ option.hint ? ` ${color.dim(`(${option.hint})`)}` : '' }`; } if (state === 'group-active') { - return `${spacingPrefix}${prefix}${color.cyan(S_CHECKBOX_ACTIVE)} ${color.dim(label)}`; + return `${spacingPrefix}${prefix}${style.checkboxUnselectedActive} ${color.dim(label)}`; } if (state === 'group-active-selected') { - return `${spacingPrefix}${prefix}${color.green(S_CHECKBOX_SELECTED)} ${color.dim(label)}`; + return `${spacingPrefix}${prefix}${style.checkboxSelectedInactive} ${color.dim(label)}`; } if (state === 'selected') { - const selectedCheckbox = isItem || selectableGroups ? color.green(S_CHECKBOX_SELECTED) : ''; + const selectedCheckbox = isItem || selectableGroups ? style.checkboxSelectedInactive : ''; return `${spacingPrefix}${color.dim(prefix)}${selectedCheckbox} ${color.dim(label)}${ option.hint ? ` ${color.dim(`(${option.hint})`)}` : '' }`; @@ -67,14 +69,14 @@ export const groupMultiselect = (opts: GroupMultiSelectOptions) => return `${color.strikethrough(color.dim(label))}`; } if (state === 'active-selected') { - return `${spacingPrefix}${color.dim(prefix)}${color.green(S_CHECKBOX_SELECTED)} ${label}${ + return `${spacingPrefix}${color.dim(prefix)}${style.checkboxSelectedActive} ${label}${ option.hint ? ` ${color.dim(`(${option.hint})`)}` : '' }`; } if (state === 'submitted') { return `${color.dim(label)}`; } - const unselectedCheckbox = isItem || selectableGroups ? color.dim(S_CHECKBOX_INACTIVE) : ''; + const unselectedCheckbox = isItem || selectableGroups ? style.checkboxUnselectedInactive : ''; return `${spacingPrefix}${color.dim(prefix)}${unselectedCheckbox} ${color.dim(label)}`; }; const required = opts.required ?? true; @@ -99,35 +101,37 @@ export const groupMultiselect = (opts: GroupMultiSelectOptions) => )}`; }, render() { - const title = `${color.gray(S_BAR)}\n${symbol(this.state)} ${opts.message}\n`; + const themeColor = getThemeColor(this.state); + const themePrefix = getThemePrefix(this.state); + + const bar = style[themeColor](S_BAR); + const barEnd = style[themeColor](S_BAR_END); + + const title = `${color.gray(S_BAR)}\n${style[themePrefix]} ${opts.message}\n`; const value = this.value ?? []; switch (this.state) { case 'submit': { const selectedOptions = this.options .filter(({ value: optionValue }) => value.includes(optionValue)) - .map((option) => opt(option, 'submitted')); + .map((option) => opt(option, 'submitted', this.options, bar)); const optionsText = selectedOptions.length === 0 ? '' : ` ${selectedOptions.join(color.dim(', '))}`; - return `${title}${color.gray(S_BAR)}${optionsText}`; + return `${title}${bar}${optionsText}`; } case 'cancel': { const label = this.options .filter(({ value: optionValue }) => value.includes(optionValue)) - .map((option) => opt(option, 'cancelled')) + .map((option) => opt(option, 'cancelled', this.options, bar)) .join(color.dim(', ')); - return `${title}${color.gray(S_BAR)} ${ - label.trim() ? `${label}\n${color.gray(S_BAR)}` : '' - }`; + return `${title}${bar} ${label.trim() ? `${label}\n${bar}` : ''}`; } case 'error': { const footer = this.error .split('\n') - .map((ln, i) => - i === 0 ? `${color.yellow(S_BAR_END)} ${color.yellow(ln)}` : ` ${ln}` - ) + .map((ln, i) => (i === 0 ? `${barEnd} ${style[themeColor](ln)}` : ` ${ln}`)) .join('\n'); - return `${title}${color.yellow(S_BAR)} ${this.options + return `${title}${bar} ${this.options .map((option, i, options) => { const selected = value.includes(option.value) || @@ -138,17 +142,22 @@ export const groupMultiselect = (opts: GroupMultiSelectOptions) => typeof option.group === 'string' && this.options[this.cursor].value === option.group; if (groupActive) { - return opt(option, selected ? 'group-active-selected' : 'group-active', options); + return opt( + option, + selected ? 'group-active-selected' : 'group-active', + options, + bar + ); } if (active && selected) { - return opt(option, 'active-selected', options); + return opt(option, 'active-selected', options, bar); } if (selected) { - return opt(option, 'selected', options); + return opt(option, 'selected', options, bar); } - return opt(option, active ? 'active' : 'inactive', options); + return opt(option, active ? 'active' : 'inactive', options, bar); }) - .join(`\n${color.yellow(S_BAR)} `)}\n${footer}\n`; + .join(`\n${bar} `)}\n${footer}\n`; } default: { const optionsText = this.options @@ -166,21 +175,22 @@ export const groupMultiselect = (opts: GroupMultiSelectOptions) => optionText = opt( option, selected ? 'group-active-selected' : 'group-active', - options + options, + bar ); } else if (active && selected) { - optionText = opt(option, 'active-selected', options); + optionText = opt(option, 'active-selected', options, bar); } else if (selected) { - optionText = opt(option, 'selected', options); + optionText = opt(option, 'selected', options, bar); } else { - optionText = opt(option, active ? 'active' : 'inactive', options); + optionText = opt(option, active ? 'active' : 'inactive', options, bar); } const prefix = i !== 0 && !optionText.startsWith('\n') ? ' ' : ''; return `${prefix}${optionText}`; }) - .join(`\n${color.cyan(S_BAR)}`); + .join(`\n${bar}`); const optionsPrefix = optionsText.startsWith('\n') ? '' : ' '; - return `${title}${color.cyan(S_BAR)}${optionsPrefix}${optionsText}\n${color.cyan(S_BAR_END)}\n`; + return `${title}${bar}${optionsPrefix}${optionsText}\n${barEnd}\n`; } } }, diff --git a/packages/prompts/src/multi-select.ts b/packages/prompts/src/multi-select.ts index 491b7218..4c11eda6 100644 --- a/packages/prompts/src/multi-select.ts +++ b/packages/prompts/src/multi-select.ts @@ -1,19 +1,18 @@ import { MultiSelectPrompt, wrapTextWithPrefix } from '@clack/core'; import color from 'picocolors'; import { + type CheckboxTheme, type CommonOptions, + extendStyle, + getThemeColor, + getThemePrefix, S_BAR, S_BAR_END, - S_CHECKBOX_ACTIVE, - S_CHECKBOX_INACTIVE, - S_CHECKBOX_SELECTED, - symbol, - symbolBar, } from './common.js'; import { limitOptions } from './limit-options.js'; import type { Option } from './select.js'; -export interface MultiSelectOptions extends CommonOptions { +export interface MultiSelectOptions extends CommonOptions { message: string; options: Option[]; initialValues?: Value[]; @@ -29,6 +28,7 @@ const computeLabel = (label: string, format: (text: string) => string) => { }; export const multiselect = (opts: MultiSelectOptions) => { + const style = extendStyle(opts.theme); const opt = ( option: Option, state: @@ -42,17 +42,17 @@ export const multiselect = (opts: MultiSelectOptions) => { ) => { const label = option.label ?? String(option.value); if (state === 'disabled') { - return `${color.gray(S_CHECKBOX_INACTIVE)} ${computeLabel(label, color.gray)}${ + return `${style.checkboxDisabled} ${computeLabel(label, color.gray)}${ option.hint ? ` ${color.dim(`(${option.hint ?? 'disabled'})`)}` : '' }`; } if (state === 'active') { - return `${color.cyan(S_CHECKBOX_ACTIVE)} ${label}${ + return `${style.checkboxUnselectedActive} ${label}${ option.hint ? ` ${color.dim(`(${option.hint})`)}` : '' }`; } if (state === 'selected') { - return `${color.green(S_CHECKBOX_SELECTED)} ${computeLabel(label, color.dim)}${ + return `${style.checkboxSelectedInactive} ${computeLabel(label, color.dim)}${ option.hint ? ` ${color.dim(`(${option.hint})`)}` : '' }`; } @@ -60,14 +60,14 @@ export const multiselect = (opts: MultiSelectOptions) => { return `${computeLabel(label, (text) => color.strikethrough(color.dim(text)))}`; } if (state === 'active-selected') { - return `${color.green(S_CHECKBOX_SELECTED)} ${label}${ + return `${style.checkboxSelectedActive} ${label}${ option.hint ? ` ${color.dim(`(${option.hint})`)}` : '' }`; } if (state === 'submitted') { return `${computeLabel(label, color.dim)}`; } - return `${color.dim(S_CHECKBOX_INACTIVE)} ${computeLabel(label, color.dim)}`; + return `${style.checkboxUnselectedInactive} ${computeLabel(label, color.dim)}`; }; const required = opts.required ?? true; @@ -90,11 +90,16 @@ export const multiselect = (opts: MultiSelectOptions) => { )}`; }, render() { + const themeColor = getThemeColor(this.state); + const themePrefix = getThemePrefix(this.state); + + const bar = style[themeColor](S_BAR); + const barEnd = style[themeColor](S_BAR_END); const wrappedMessage = wrapTextWithPrefix( opts.output, opts.message, - `${symbolBar(this.state)} `, - `${symbol(this.state)} ` + `${bar} `, + `${style[themePrefix]} ` ); const title = `${color.gray(S_BAR)}\n${wrappedMessage}\n`; const value = this.value ?? []; @@ -120,11 +125,7 @@ export const multiselect = (opts: MultiSelectOptions) => { .filter(({ value: optionValue }) => value.includes(optionValue)) .map((option) => opt(option, 'submitted')) .join(color.dim(', ')) || color.dim('none'); - const wrappedSubmitText = wrapTextWithPrefix( - opts.output, - submitText, - `${color.gray(S_BAR)} ` - ); + const wrappedSubmitText = wrapTextWithPrefix(opts.output, submitText, `${bar} `); return `${title}${wrappedSubmitText}`; } case 'cancel': { @@ -133,18 +134,16 @@ export const multiselect = (opts: MultiSelectOptions) => { .map((option) => opt(option, 'cancelled')) .join(color.dim(', ')); if (label.trim() === '') { - return `${title}${color.gray(S_BAR)}`; + return `${title}${bar}`; } - const wrappedLabel = wrapTextWithPrefix(opts.output, label, `${color.gray(S_BAR)} `); - return `${title}${wrappedLabel}\n${color.gray(S_BAR)}`; + const wrappedLabel = wrapTextWithPrefix(opts.output, label, `${bar} `); + return `${title}${wrappedLabel}\n${bar}`; } case 'error': { - const prefix = `${color.yellow(S_BAR)} `; + const prefix = `${bar} `; const footer = this.error .split('\n') - .map((ln, i) => - i === 0 ? `${color.yellow(S_BAR_END)} ${color.yellow(ln)}` : ` ${ln}` - ) + .map((ln, i) => (i === 0 ? `${barEnd} ${style[themeColor](ln)}` : ` ${ln}`)) .join('\n'); return `${title}${prefix}${limitOptions({ output: opts.output, @@ -156,7 +155,7 @@ export const multiselect = (opts: MultiSelectOptions) => { }).join(`\n${prefix}`)}\n${footer}\n`; } default: { - const prefix = `${color.cyan(S_BAR)} `; + const prefix = `${bar} `; return `${title}${prefix}${limitOptions({ output: opts.output, options: this.options, @@ -164,7 +163,7 @@ export const multiselect = (opts: MultiSelectOptions) => { maxItems: opts.maxItems, columnPadding: prefix.length, style: styleOption, - }).join(`\n${prefix}`)}\n${color.cyan(S_BAR_END)}\n`; + }).join(`\n${prefix}`)}\n${barEnd}\n`; } } }, diff --git a/packages/prompts/src/password.ts b/packages/prompts/src/password.ts index 8010960b..ad7fcb70 100644 --- a/packages/prompts/src/password.ts +++ b/packages/prompts/src/password.ts @@ -1,14 +1,24 @@ import { PasswordPrompt } from '@clack/core'; import color from 'picocolors'; -import { type CommonOptions, S_BAR, S_BAR_END, S_PASSWORD_MASK, symbol } from './common.js'; +import { + type CommonOptions, + extendStyle, + getThemeColor, + getThemePrefix, + S_BAR, + S_BAR_END, + S_PASSWORD_MASK, +} from './common.js'; -export interface PasswordOptions extends CommonOptions { +export interface PasswordOptions extends CommonOptions<{}> { message: string; mask?: string; validate?: (value: string | undefined) => string | Error | undefined; clearOnError?: boolean; } export const password = (opts: PasswordOptions) => { + const style = extendStyle<{}>(opts.theme); + return new PasswordPrompt({ validate: opts.validate, mask: opts.mask ?? S_PASSWORD_MASK, @@ -16,7 +26,13 @@ export const password = (opts: PasswordOptions) => { input: opts.input, output: opts.output, render() { - const title = `${color.gray(S_BAR)}\n${symbol(this.state)} ${opts.message}\n`; + const themeColor = getThemeColor(this.state); + const themePrefix = getThemePrefix(this.state); + + const bar = style[themeColor](S_BAR); + const barEnd = style[themeColor](S_BAR_END); + + const title = `${color.gray(S_BAR)}\n${style[themePrefix]} ${opts.message}\n`; const userInput = this.userInputWithCursor; const masked = this.masked; @@ -26,22 +42,18 @@ export const password = (opts: PasswordOptions) => { if (opts.clearOnError) { this.clear(); } - return `${title.trim()}\n${color.yellow(S_BAR)}${maskedText}\n${color.yellow( - S_BAR_END - )} ${color.yellow(this.error)}\n`; + return `${title.trim()}\n${bar}${maskedText}\n${barEnd} ${style[themeColor](this.error)}\n`; } case 'submit': { const maskedText = masked ? ` ${color.dim(masked)}` : ''; - return `${title}${color.gray(S_BAR)}${maskedText}`; + return `${title}${bar}${maskedText}`; } case 'cancel': { const maskedText = masked ? ` ${color.strikethrough(color.dim(masked))}` : ''; - return `${title}${color.gray(S_BAR)}${maskedText}${ - masked ? `\n${color.gray(S_BAR)}` : '' - }`; + return `${title}${bar}${maskedText}${masked ? `\n${bar}` : ''}`; } default: - return `${title}${color.cyan(S_BAR)} ${userInput}\n${color.cyan(S_BAR_END)}\n`; + return `${title}${bar} ${userInput}\n${barEnd}\n`; } }, }).prompt() as Promise; diff --git a/packages/prompts/src/select-key.ts b/packages/prompts/src/select-key.ts index f5bbbf69..61a8977a 100644 --- a/packages/prompts/src/select-key.ts +++ b/packages/prompts/src/select-key.ts @@ -1,9 +1,10 @@ import { SelectKeyPrompt } from '@clack/core'; import color from 'picocolors'; -import { S_BAR, S_BAR_END, symbol } from './common.js'; +import { extendStyle, getThemeColor, getThemePrefix, S_BAR, S_BAR_END } from './common.js'; import type { Option, SelectOptions } from './select.js'; export const selectKey = (opts: SelectOptions) => { + const style = extendStyle<{}>(opts.theme); const opt = ( option: Option, state: 'inactive' | 'active' | 'selected' | 'cancelled' = 'inactive' @@ -32,22 +33,24 @@ export const selectKey = (opts: SelectOptions) => { output: opts.output, initialValue: opts.initialValue, render() { - const title = `${color.gray(S_BAR)}\n${symbol(this.state)} ${opts.message}\n`; + const themeColor = getThemeColor(this.state); + const themePrefix = getThemePrefix(this.state); + + const title = `${color.gray(S_BAR)}\n${style[themePrefix]} ${opts.message}\n`; + const bar = style[themeColor](S_BAR); switch (this.state) { case 'submit': - return `${title}${color.gray(S_BAR)} ${opt( + return `${title}${bar} ${opt( this.options.find((opt) => opt.value === this.value) ?? opts.options[0], 'selected' )}`; case 'cancel': - return `${title}${color.gray(S_BAR)} ${opt(this.options[0], 'cancelled')}\n${color.gray( - S_BAR - )}`; + return `${title}${bar} ${opt(this.options[0], 'cancelled')}\n${bar}`; default: { - return `${title}${color.cyan(S_BAR)} ${this.options + return `${title}${bar} ${this.options .map((option, i) => opt(option, i === this.cursor ? 'active' : 'inactive')) - .join(`\n${color.cyan(S_BAR)} `)}\n${color.cyan(S_BAR_END)}\n`; + .join(`\n${bar} `)}\n${style[themeColor](S_BAR_END)}\n`; } } }, diff --git a/packages/prompts/src/select.ts b/packages/prompts/src/select.ts index b6bb6721..d825a5e1 100644 --- a/packages/prompts/src/select.ts +++ b/packages/prompts/src/select.ts @@ -2,12 +2,12 @@ import { SelectPrompt, wrapTextWithPrefix } from '@clack/core'; import color from 'picocolors'; import { type CommonOptions, + extendStyle, + getThemeColor, + getThemePrefix, + type RadioTheme, S_BAR, S_BAR_END, - S_RADIO_ACTIVE, - S_RADIO_INACTIVE, - symbol, - symbolBar, } from './common.js'; import { limitOptions } from './limit-options.js'; @@ -65,7 +65,7 @@ export type Option = Value extends Primitive disabled?: boolean; }; -export interface SelectOptions extends CommonOptions { +export interface SelectOptions extends CommonOptions { message: string; options: Option[]; initialValue?: Value; @@ -83,6 +83,7 @@ const computeLabel = (label: string, format: (text: string) => string) => { }; export const select = (opts: SelectOptions) => { + const style = extendStyle(opts.theme); const opt = ( option: Option, state: 'inactive' | 'active' | 'selected' | 'cancelled' | 'disabled' @@ -90,19 +91,19 @@ export const select = (opts: SelectOptions) => { const label = option.label ?? String(option.value); switch (state) { case 'disabled': - return `${color.gray(S_RADIO_INACTIVE)} ${computeLabel(label, color.gray)}${ + return `${style.radioDisabled} ${computeLabel(label, color.gray)}${ option.hint ? ` ${color.dim(`(${option.hint ?? 'disabled'})`)}` : '' }`; case 'selected': return `${computeLabel(label, color.dim)}`; case 'active': - return `${color.green(S_RADIO_ACTIVE)} ${label}${ + return `${style.radioActive} ${label}${ option.hint ? ` ${color.dim(`(${option.hint})`)}` : '' }`; case 'cancelled': return `${computeLabel(label, (str) => color.strikethrough(color.dim(str)))}`; default: - return `${color.dim(S_RADIO_INACTIVE)} ${computeLabel(label, color.dim)}`; + return `${style.radioInactive} ${computeLabel(label, color.dim)}`; } }; @@ -113,8 +114,12 @@ export const select = (opts: SelectOptions) => { output: opts.output, initialValue: opts.initialValue, render() { - const titlePrefix = `${symbol(this.state)} `; - const titlePrefixBar = `${symbolBar(this.state)} `; + const themeColor = getThemeColor(this.state); + const themePrefix = getThemePrefix(this.state); + + const bar = style[themeColor](S_BAR); + const titlePrefix = `${style[themePrefix]} `; + const titlePrefixBar = `${bar} `; const messageLines = wrapTextWithPrefix( opts.output, opts.message, @@ -125,7 +130,7 @@ export const select = (opts: SelectOptions) => { switch (this.state) { case 'submit': { - const submitPrefix = `${color.gray(S_BAR)} `; + const submitPrefix = `${bar} `; const wrappedLines = wrapTextWithPrefix( opts.output, opt(this.options[this.cursor], 'selected'), @@ -134,16 +139,16 @@ export const select = (opts: SelectOptions) => { return `${title}${wrappedLines}`; } case 'cancel': { - const cancelPrefix = `${color.gray(S_BAR)} `; + const cancelPrefix = `${bar} `; const wrappedLines = wrapTextWithPrefix( opts.output, opt(this.options[this.cursor], 'cancelled'), cancelPrefix ); - return `${title}${wrappedLines}\n${color.gray(S_BAR)}`; + return `${title}${wrappedLines}\n${bar}`; } default: { - const prefix = `${color.cyan(S_BAR)} `; + const prefix = `${bar} `; return `${title}${prefix}${limitOptions({ output: opts.output, cursor: this.cursor, @@ -152,7 +157,7 @@ export const select = (opts: SelectOptions) => { columnPadding: prefix.length, style: (item, active) => opt(item, item.disabled ? 'disabled' : active ? 'active' : 'inactive'), - }).join(`\n${prefix}`)}\n${color.cyan(S_BAR_END)}\n`; + }).join(`\n${prefix}`)}\n${style[themeColor](S_BAR_END)}\n`; } } }, diff --git a/packages/prompts/src/text.ts b/packages/prompts/src/text.ts index 244f7c8d..6d1373cf 100644 --- a/packages/prompts/src/text.ts +++ b/packages/prompts/src/text.ts @@ -1,8 +1,15 @@ import { TextPrompt } from '@clack/core'; import color from 'picocolors'; -import { type CommonOptions, S_BAR, S_BAR_END, symbol } from './common.js'; +import { + type CommonOptions, + extendStyle, + getThemeColor, + getThemePrefix, + S_BAR, + S_BAR_END, +} from './common.js'; -export interface TextOptions extends CommonOptions { +export interface TextOptions extends CommonOptions<{}> { message: string; placeholder?: string; defaultValue?: string; @@ -11,6 +18,8 @@ export interface TextOptions extends CommonOptions { } export const text = (opts: TextOptions) => { + const style = extendStyle<{}>(opts.theme); + return new TextPrompt({ validate: opts.validate, placeholder: opts.placeholder, @@ -20,7 +29,13 @@ export const text = (opts: TextOptions) => { signal: opts.signal, input: opts.input, render() { - const title = `${color.gray(S_BAR)}\n${symbol(this.state)} ${opts.message}\n`; + const themeColor = getThemeColor(this.state); + const themePrefix = getThemePrefix(this.state); + + const bar = style[themeColor](S_BAR); + const barEnd = style[themeColor](S_BAR_END); + + const title = `${color.gray(S_BAR)}\n${style[themePrefix]} ${opts.message}\n`; const placeholder = opts.placeholder ? color.inverse(opts.placeholder[0]) + color.dim(opts.placeholder.slice(1)) : color.inverse(color.hidden('_')); @@ -29,21 +44,19 @@ export const text = (opts: TextOptions) => { switch (this.state) { case 'error': { - const errorText = this.error ? ` ${color.yellow(this.error)}` : ''; - return `${title.trim()}\n${color.yellow(S_BAR)} ${userInput}\n${color.yellow( - S_BAR_END - )}${errorText}\n`; + const errorText = this.error ? ` ${style[themeColor](this.error)}` : ''; + return `${title.trim()}\n${bar} ${userInput}\n${barEnd}${errorText}\n`; } case 'submit': { const valueText = value ? ` ${color.dim(value)}` : ''; - return `${title}${color.gray(S_BAR)}${valueText}`; + return `${title}${bar}${valueText}`; } case 'cancel': { const valueText = value ? ` ${color.strikethrough(color.dim(value))}` : ''; - return `${title}${color.gray(S_BAR)}${valueText}${value.trim() ? `\n${color.gray(S_BAR)}` : ''}`; + return `${title}${bar}${valueText}${value.trim() ? `\n${bar}` : ''}`; } default: - return `${title}${color.cyan(S_BAR)} ${userInput}\n${color.cyan(S_BAR_END)}\n`; + return `${title}${bar} ${userInput}\n${barEnd}\n`; } }, }).prompt() as Promise; diff --git a/packages/prompts/test/__snapshots__/autocomplete.test.ts.snap b/packages/prompts/test/__snapshots__/autocomplete.test.ts.snap index e7c7fa35..7ed854de 100644 --- a/packages/prompts/test/__snapshots__/autocomplete.test.ts.snap +++ b/packages/prompts/test/__snapshots__/autocomplete.test.ts.snap @@ -340,9 +340,8 @@ exports[`autocompleteMultiselect > can be aborted by a signal 1`] = ` "", "│ ◆ foo - │ Search: _ -│ ◻ Apple +│ ◻ Apple │ ◻ Banana │ ◻ Cherry │ ◻ Grape @@ -360,46 +359,45 @@ exports[`autocompleteMultiselect > can use navigation keys to select options 1`] "", "│ ◆ Select fruits - │ Search: _ -│ ◻ Apple +│ ◻ Apple │ ◻ Banana │ ◻ Cherry │ ◻ Grape │ ◻ Orange │ ↑/↓ to navigate • Tab: select • Enter: confirm • Type: to search └", - "", - "", + "", + "", "", "│ Search:  │ ◻ Apple -│ ◻ Banana +│ ◻ Banana │ ◻ Cherry │ ◻ Grape │ ◻ Orange │ ↑/↓ to navigate • Space/Tab: select • Enter: confirm • Type: to search └", - "", - "", + "", + "", "", "│ ◼ Banana", "", - "", - "", + "", + "", "", "│ ◼ Banana -│ ◻ Cherry +│ ◻ Cherry │ ◻ Grape │ ◻ Orange │ ↑/↓ to navigate • Space/Tab: select • Enter: confirm • Type: to search └", - "", - "", + "", + "", "", "│ ◼ Cherry", "", - "", + "", "", "", "◇ Select fruits @@ -415,34 +413,31 @@ exports[`autocompleteMultiselect > renders error when empty selection & required "", "│ ◆ Select a fruit - │ Search: _ -│ ◻ Apple +│ ◻ Apple │ ◻ Banana │ ◻ Cherry │ ◻ Grape │ ◻ Orange │ ↑/↓ to navigate • Tab: select • Enter: confirm • Type: to search └", - "", + "", "", "", "▲ Select a fruit - -│ Search: _ -│ Please select at least one item -│ ◻ Apple -│ ◻ Banana -│ ◻ Cherry -│ ◻ Grape -│ ◻ Orange -│ ↑/↓ to navigate • Tab: select • Enter: confirm • Type: to search -└", - "", +│ Search: _ +│ Please select at least one item +│ ◻ Apple +│ ◻ Banana +│ ◻ Cherry +│ ◻ Grape +│ ◻ Orange +│ ↑/↓ to navigate • Tab: select • Enter: confirm • Type: to search +└", + "", "", "", "◆ Select a fruit - │ Search: _ │ ◼ Apple │ ◻ Banana @@ -451,7 +446,7 @@ exports[`autocompleteMultiselect > renders error when empty selection & required │ ◻ Orange │ ↑/↓ to navigate • Tab: select • Enter: confirm • Type: to search └", - "", + "", "", "", "◇ Select a fruit diff --git a/packages/prompts/test/__snapshots__/multi-select.test.ts.snap b/packages/prompts/test/__snapshots__/multi-select.test.ts.snap index 0a064065..6891c9d2 100644 --- a/packages/prompts/test/__snapshots__/multi-select.test.ts.snap +++ b/packages/prompts/test/__snapshots__/multi-select.test.ts.snap @@ -691,8 +691,8 @@ exports[`multiselect (isCI = false) > wraps long messages 1`] = ` "", "", "◇ foo foo foo foo foo foo foo -│ foo foo foo foo foo foo -│ foo foo foo foo foo foo foo +│ foo foo foo foo foo foo +│ foo foo foo foo foo foo foo │ opt0", " ", @@ -1425,8 +1425,8 @@ exports[`multiselect (isCI = true) > wraps long messages 1`] = ` "", "", "◇ foo foo foo foo foo foo foo -│ foo foo foo foo foo foo -│ foo foo foo foo foo foo foo +│ foo foo foo foo foo foo +│ foo foo foo foo foo foo foo │ opt0", " ", diff --git a/packages/prompts/test/__snapshots__/path.test.ts.snap b/packages/prompts/test/__snapshots__/path.test.ts.snap index f96e4641..ff0ceef8 100644 --- a/packages/prompts/test/__snapshots__/path.test.ts.snap +++ b/packages/prompts/test/__snapshots__/path.test.ts.snap @@ -44,12 +44,12 @@ exports[`text (isCI = false) > cannot submit unknown value 1`] = ` "", "", "▲ foo -│ -│ Search: /tmp/_█ -│ No matches found +│ +│ Search: /tmp/_█ +│ No matches found │ Please select a path -│ ↑/↓ to select • Enter: confirm • Type: to search -└", +│ ↑/↓ to select • Enter: confirm • Type: to search +└", "", "", "", @@ -211,12 +211,12 @@ exports[`text (isCI = false) > validation errors render and clear (using Error) "", "", "▲ foo -│ -│ Search: /tmp/r█ +│ +│ Search: /tmp/r█ │ should be /tmp/bar -│ ● /tmp/root.zip -│ ↑/↓ to select • Enter: confirm • Type: to search -└", +│ ● /tmp/root.zip +│ ↑/↓ to select • Enter: confirm • Type: to search +└", "", "", "", @@ -267,12 +267,12 @@ exports[`text (isCI = false) > validation errors render and clear 1`] = ` "", "", "▲ foo -│ -│ Search: /tmp/r█ +│ +│ Search: /tmp/r█ │ should be /tmp/bar -│ ● /tmp/root.zip -│ ↑/↓ to select • Enter: confirm • Type: to search -└", +│ ● /tmp/root.zip +│ ↑/↓ to select • Enter: confirm • Type: to search +└", "", "", "", @@ -345,12 +345,12 @@ exports[`text (isCI = true) > cannot submit unknown value 1`] = ` "", "", "▲ foo -│ -│ Search: /tmp/_█ -│ No matches found +│ +│ Search: /tmp/_█ +│ No matches found │ Please select a path -│ ↑/↓ to select • Enter: confirm • Type: to search -└", +│ ↑/↓ to select • Enter: confirm • Type: to search +└", "", "", "", @@ -512,12 +512,12 @@ exports[`text (isCI = true) > validation errors render and clear (using Error) 1 "", "", "▲ foo -│ -│ Search: /tmp/r█ +│ +│ Search: /tmp/r█ │ should be /tmp/bar -│ ● /tmp/root.zip -│ ↑/↓ to select • Enter: confirm • Type: to search -└", +│ ● /tmp/root.zip +│ ↑/↓ to select • Enter: confirm • Type: to search +└", "", "", "", @@ -568,12 +568,12 @@ exports[`text (isCI = true) > validation errors render and clear 1`] = ` "", "", "▲ foo -│ -│ Search: /tmp/r█ +│ +│ Search: /tmp/r█ │ should be /tmp/bar -│ ● /tmp/root.zip -│ ↑/↓ to select • Enter: confirm • Type: to search -└", +│ ● /tmp/root.zip +│ ↑/↓ to select • Enter: confirm • Type: to search +└", "", "", "", diff --git a/packages/prompts/test/__snapshots__/select.test.ts.snap b/packages/prompts/test/__snapshots__/select.test.ts.snap index 1332d878..4a49135e 100644 --- a/packages/prompts/test/__snapshots__/select.test.ts.snap +++ b/packages/prompts/test/__snapshots__/select.test.ts.snap @@ -289,8 +289,8 @@ exports[`select (isCI = false) > wraps long messages 1`] = ` "", "", "◇ foo foo foo foo foo foo foo -│ foo foo foo foo foo foo -│ foo foo foo foo foo foo foo +│ foo foo foo foo foo foo +│ foo foo foo foo foo foo foo │ opt0", " ", @@ -615,8 +615,8 @@ exports[`select (isCI = true) > wraps long messages 1`] = ` "", "", "◇ foo foo foo foo foo foo foo -│ foo foo foo foo foo foo -│ foo foo foo foo foo foo foo +│ foo foo foo foo foo foo +│ foo foo foo foo foo foo foo │ opt0", " ", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2c19acf1..21d2b4f5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -100,10 +100,10 @@ importers: version: 4.17.2 vitest: specifier: ^3.2.4 - version: 3.2.4(@types/node@24.1.0)(jiti@2.5.0) + version: 3.2.4 vitest-ansi-serializer: specifier: ^0.1.2 - version: 0.1.2(vitest@3.2.4(@types/node@24.1.0)(jiti@2.5.0)) + version: 0.1.2(vitest@3.2.4) packages: @@ -2378,6 +2378,14 @@ snapshots: optionalDependencies: vite: 7.0.6(@types/node@24.1.0)(jiti@2.5.0) + '@vitest/mocker@3.2.4(vite@7.0.6)': + dependencies: + '@vitest/spy': 3.2.4 + estree-walker: 3.0.3 + magic-string: 0.30.17 + optionalDependencies: + vite: 7.0.6 + '@vitest/pretty-format@3.2.4': dependencies: tinyrainbow: 2.0.0 @@ -3401,6 +3409,27 @@ snapshots: util-deprecate@1.0.2: {} + vite-node@3.2.4: + dependencies: + cac: 6.7.14 + debug: 4.4.1 + es-module-lexer: 1.7.0 + pathe: 2.0.3 + vite: 7.0.6 + transitivePeerDependencies: + - '@types/node' + - jiti + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + vite-node@3.2.4(@types/node@24.1.0)(jiti@2.5.0): dependencies: cac: 6.7.14 @@ -3422,6 +3451,17 @@ snapshots: - tsx - yaml + vite@7.0.6: + dependencies: + esbuild: 0.25.8 + fdir: 6.4.6(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.45.1 + tinyglobby: 0.2.14 + optionalDependencies: + fsevents: 2.3.3 + vite@7.0.6(@types/node@24.1.0)(jiti@2.5.0): dependencies: esbuild: 0.25.8 @@ -3435,9 +3475,48 @@ snapshots: fsevents: 2.3.3 jiti: 2.5.0 - vitest-ansi-serializer@0.1.2(vitest@3.2.4(@types/node@24.1.0)(jiti@2.5.0)): + vitest-ansi-serializer@0.1.2(vitest@3.2.4): dependencies: - vitest: 3.2.4(@types/node@24.1.0)(jiti@2.5.0) + vitest: 3.2.4 + + vitest@3.2.4: + dependencies: + '@types/chai': 5.2.2 + '@vitest/expect': 3.2.4 + '@vitest/mocker': 3.2.4(vite@7.0.6) + '@vitest/pretty-format': 3.2.4 + '@vitest/runner': 3.2.4 + '@vitest/snapshot': 3.2.4 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 + chai: 5.2.0 + debug: 4.4.1 + expect-type: 1.2.1 + magic-string: 0.30.17 + pathe: 2.0.3 + picomatch: 4.0.3 + std-env: 3.9.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinyglobby: 0.2.14 + tinypool: 1.1.1 + tinyrainbow: 2.0.0 + vite: 7.0.6 + vite-node: 3.2.4 + why-is-node-running: 2.3.0 + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml vitest@3.2.4(@types/node@24.1.0)(jiti@2.5.0): dependencies: