From 5522fdbecbdd623e91331bd622d07fe324b89cac Mon Sep 17 00:00:00 2001 From: Paul Valladares <85648028+dreyfus92@users.noreply.github.com> Date: Wed, 15 Oct 2025 13:11:55 -0500 Subject: [PATCH 1/4] refactor(core + prompts): replace picocolors with styleText utility for consistent styling across prompts --- packages/core/package.json | 1 - packages/core/src/prompts/autocomplete.ts | 6 +- packages/core/src/prompts/password.ts | 6 +- packages/core/src/prompts/text.ts | 5 +- packages/core/test/prompts/password.test.ts | 12 ++- packages/core/test/prompts/text.test.ts | 4 +- packages/prompts/package.json | 1 - packages/prompts/src/autocomplete.ts | 88 ++++++++++++--------- packages/prompts/src/common.ts | 10 +-- packages/prompts/src/confirm.ts | 27 ++++--- packages/prompts/src/group-multi-select.ts | 67 +++++++++------- packages/prompts/src/limit-options.ts | 4 +- packages/prompts/src/log.ts | 16 ++-- packages/prompts/src/messages.ts | 8 +- packages/prompts/src/multi-select.ts | 53 +++++++------ packages/prompts/src/note.ts | 12 +-- packages/prompts/src/password.ts | 24 +++--- packages/prompts/src/progress-bar.ts | 12 +-- packages/prompts/src/select-key.ts | 25 +++--- packages/prompts/src/select.ts | 24 +++--- packages/prompts/src/spinner.ts | 12 +-- packages/prompts/src/stream.ts | 19 +++-- packages/prompts/src/task-log.ts | 32 +++++--- packages/prompts/src/text.ts | 23 +++--- packages/prompts/test/box.test.ts | 4 +- packages/prompts/test/limit-options.test.ts | 24 +++--- packages/prompts/test/note.test.ts | 8 +- pnpm-lock.yaml | 6 -- 28 files changed, 285 insertions(+), 248 deletions(-) diff --git a/packages/core/package.json b/packages/core/package.json index 3670fa86..713b958c 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -53,7 +53,6 @@ "test": "vitest run" }, "dependencies": { - "picocolors": "^1.0.0", "sisteransi": "^1.0.5" }, "devDependencies": { diff --git a/packages/core/src/prompts/autocomplete.ts b/packages/core/src/prompts/autocomplete.ts index 6e308ff5..c636d92e 100644 --- a/packages/core/src/prompts/autocomplete.ts +++ b/packages/core/src/prompts/autocomplete.ts @@ -1,5 +1,5 @@ import type { Key } from 'node:readline'; -import color from 'picocolors'; +import { styleText } from 'node:util'; import Prompt, { type PromptOptions } from './prompt.js'; interface OptionLike { @@ -71,14 +71,14 @@ export default class AutocompletePrompt extends Prompt< get userInputWithCursor() { if (!this.userInput) { - return color.inverse(color.hidden('_')); + return styleText('inverse', styleText('hidden', '_')); } if (this._cursor >= this.userInput.length) { return `${this.userInput}█`; } const s1 = this.userInput.slice(0, this._cursor); const [s2, ...s3] = this.userInput.slice(this._cursor); - return `${s1}${color.inverse(s2)}${s3.join('')}`; + return `${s1}${styleText('inverse', s2)}${s3.join('')}`; } get options(): T[] { diff --git a/packages/core/src/prompts/password.ts b/packages/core/src/prompts/password.ts index a9864577..331b8fa1 100644 --- a/packages/core/src/prompts/password.ts +++ b/packages/core/src/prompts/password.ts @@ -1,4 +1,4 @@ -import color from 'picocolors'; +import { styleText } from 'node:util'; import Prompt, { type PromptOptions } from './prompt.js'; interface PasswordOptions extends PromptOptions { @@ -18,12 +18,12 @@ export default class PasswordPrompt extends Prompt { } const userInput = this.userInput; if (this.cursor >= userInput.length) { - return `${this.masked}${color.inverse(color.hidden('_'))}`; + return `${this.masked}${styleText('inverse', styleText('hidden', '_'))}`; } const masked = this.masked; const s1 = masked.slice(0, this.cursor); const s2 = masked.slice(this.cursor); - return `${s1}${color.inverse(s2[0])}${s2.slice(1)}`; + return `${s1}${styleText('inverse', s2[0])}${s2.slice(1)}`; } clear() { this._clearUserInput(); diff --git a/packages/core/src/prompts/text.ts b/packages/core/src/prompts/text.ts index c76a5188..2e70bf6b 100644 --- a/packages/core/src/prompts/text.ts +++ b/packages/core/src/prompts/text.ts @@ -1,4 +1,5 @@ -import color from 'picocolors'; +// import color from 'picocolors'; +import { styleText } from 'node:util'; import Prompt, { type PromptOptions } from './prompt.js'; interface TextOptions extends PromptOptions { @@ -17,7 +18,7 @@ export default class TextPrompt extends Prompt { } const s1 = userInput.slice(0, this.cursor); const [s2, ...s3] = userInput.slice(this.cursor); - return `${s1}${color.inverse(s2)}${s3.join('')}`; + return `${s1}${styleText('inverse', s2)}${s3.join('')}`; } get cursor() { return this._cursor; diff --git a/packages/core/test/prompts/password.test.ts b/packages/core/test/prompts/password.test.ts index dc145fc7..33d1c584 100644 --- a/packages/core/test/prompts/password.test.ts +++ b/packages/core/test/prompts/password.test.ts @@ -1,4 +1,4 @@ -import color from 'picocolors'; +import { styleText } from 'node:util'; import { cursor } from 'sisteransi'; import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; import { default as PasswordPrompt } from '../../src/prompts/password.js'; @@ -65,7 +65,9 @@ describe('PasswordPrompt', () => { }); instance.prompt(); input.emit('keypress', 'x', { name: 'x' }); - expect(instance.userInputWithCursor).to.equal(`•${color.inverse(color.hidden('_'))}`); + expect(instance.userInputWithCursor).to.equal( + `•${styleText('inverse', styleText('hidden', '_'))}` + ); }); test('renders cursor inside value', () => { @@ -80,7 +82,7 @@ describe('PasswordPrompt', () => { input.emit('keypress', 'z', { name: 'z' }); input.emit('keypress', 'left', { name: 'left' }); input.emit('keypress', 'left', { name: 'left' }); - expect(instance.userInputWithCursor).to.equal(`•${color.inverse('•')}•`); + expect(instance.userInputWithCursor).to.equal(`•${styleText('inverse', '•')}•`); }); test('renders custom mask', () => { @@ -92,7 +94,9 @@ describe('PasswordPrompt', () => { }); instance.prompt(); input.emit('keypress', 'x', { name: 'x' }); - expect(instance.userInputWithCursor).to.equal(`X${color.inverse(color.hidden('_'))}`); + expect(instance.userInputWithCursor).to.equal( + `X${styleText('inverse', styleText('hidden', '_'))}` + ); }); }); }); diff --git a/packages/core/test/prompts/text.test.ts b/packages/core/test/prompts/text.test.ts index 9ae20333..0372d174 100644 --- a/packages/core/test/prompts/text.test.ts +++ b/packages/core/test/prompts/text.test.ts @@ -1,4 +1,4 @@ -import color from 'picocolors'; +import { styleText } from 'node:util'; import { cursor } from 'sisteransi'; import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; import { default as TextPrompt } from '../../src/prompts/text.js'; @@ -93,7 +93,7 @@ describe('TextPrompt', () => { input.emit('keypress', keys[i], { name: keys[i] }); } input.emit('keypress', 'left', { name: 'left' }); - expect(instance.userInputWithCursor).to.equal(`fo${color.inverse('o')}`); + expect(instance.userInputWithCursor).to.equal(`fo${styleText('inverse', 'o')}`); }); test('shows cursor at end if beyond value', () => { diff --git a/packages/prompts/package.json b/packages/prompts/package.json index 48d0ee5d..b1b67420 100644 --- a/packages/prompts/package.json +++ b/packages/prompts/package.json @@ -54,7 +54,6 @@ }, "dependencies": { "@clack/core": "workspace:*", - "picocolors": "^1.0.0", "sisteransi": "^1.0.5" }, "devDependencies": { diff --git a/packages/prompts/src/autocomplete.ts b/packages/prompts/src/autocomplete.ts index e55b285f..4ec5fca0 100644 --- a/packages/prompts/src/autocomplete.ts +++ b/packages/prompts/src/autocomplete.ts @@ -1,5 +1,5 @@ +import { styleText } from 'node:util'; import { AutocompletePrompt } from '@clack/core'; -import color from 'picocolors'; import { type CommonOptions, S_BAR, @@ -89,7 +89,7 @@ export const autocomplete = (opts: AutocompleteOptions) => { validate: opts.validate, render() { // Title and message display - const headings = [`${color.gray(S_BAR)}`, `${symbol(this.state)} ${opts.message}`]; + const headings = [`${styleText('gray', S_BAR)}`, `${symbol(this.state)} ${opts.message}`]; const userInput = this.userInput; const valueAsString = String(this.value ?? ''); const options = this.options; @@ -102,13 +102,15 @@ export const autocomplete = (opts: AutocompleteOptions) => { // Show selected value 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}`; + selected.length > 0 ? ` ${styleText('dim', selected.map(getLabel).join(', '))}` : ''; + return `${headings.join('\n')}\n${styleText('gray', S_BAR)}${label}`; } case 'cancel': { - const userInputText = userInput ? ` ${color.strikethrough(color.dim(userInput))}` : ''; - return `${headings.join('\n')}\n${color.gray(S_BAR)}${userInputText}`; + const userInputText = userInput + ? ` ${styleText('strikethrough', styleText('dim', userInput))}` + : ''; + return `${headings.join('\n')}\n${styleText('gray', S_BAR)}${userInputText}`; } default: { @@ -116,7 +118,7 @@ export const autocomplete = (opts: AutocompleteOptions) => { let searchText = ''; if (this.isNavigating || showPlaceholder) { const searchTextValue = showPlaceholder ? placeholder : userInput; - searchText = searchTextValue !== '' ? ` ${color.dim(searchTextValue)}` : ''; + searchText = searchTextValue !== '' ? ` ${styleText('dim', searchTextValue)}` : ''; } else { searchText = ` ${this.userInputWithCursor}`; } @@ -124,7 +126,8 @@ export const autocomplete = (opts: AutocompleteOptions) => { // Show match count if filtered const matches = this.filteredOptions.length !== options.length - ? color.dim( + ? styleText( + 'dim', ` (${this.filteredOptions.length} match${this.filteredOptions.length === 1 ? '' : 'es'})` ) : ''; @@ -132,29 +135,31 @@ export const autocomplete = (opts: AutocompleteOptions) => { // No matches message const noResults = this.filteredOptions.length === 0 && userInput - ? [`${color.cyan(S_BAR)} ${color.yellow('No matches found')}`] + ? [`${styleText('cyan', S_BAR)} ${styleText('yellow', 'No matches found')}`] : []; const validationError = - this.state === 'error' ? [`${color.yellow(S_BAR)} ${color.yellow(this.error)}`] : []; + this.state === 'error' + ? [`${styleText('yellow', S_BAR)} ${styleText('yellow', this.error)}`] + : []; headings.push( - `${color.cyan(S_BAR)}`, - `${color.cyan(S_BAR)} ${color.dim('Search:')}${searchText}${matches}`, + `${styleText('cyan', S_BAR)}`, + `${styleText('cyan', S_BAR)} ${styleText('dim', 'Search:')}${searchText}${matches}`, ...noResults, ...validationError ); // Show instructions const instructions = [ - `${color.dim('↑/↓')} to select`, - `${color.dim('Enter:')} confirm`, - `${color.dim('Type:')} to search`, + `${styleText('dim', '↑/↓')} to select`, + `${styleText('dim', 'Enter:')} confirm`, + `${styleText('dim', 'Type:')} to search`, ]; const footers = [ - `${color.cyan(S_BAR)} ${color.dim(instructions.join(' • '))}`, - `${color.cyan(S_BAR_END)}`, + `${styleText('cyan', S_BAR)} ${styleText('dim', instructions.join(' • '))}`, + `${styleText('cyan', S_BAR_END)}`, ]; // Render options with selection @@ -170,12 +175,12 @@ export const autocomplete = (opts: AutocompleteOptions) => { const label = getLabel(option); const hint = option.hint && option.value === this.focusedValue - ? color.dim(` (${option.hint})`) + ? styleText('dim', ` (${option.hint})`) : ''; return active - ? `${color.green(S_RADIO_ACTIVE)} ${label}${hint}` - : `${color.dim(S_RADIO_INACTIVE)} ${color.dim(label)}${hint}`; + ? `${styleText('green', S_RADIO_ACTIVE)} ${label}${hint}` + : `${styleText('dim', S_RADIO_INACTIVE)} ${styleText('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) => `${styleText('cyan', S_BAR)} ${option}`), ...footers, ].join('\n'); } @@ -222,14 +227,16 @@ export const autocompleteMultiselect = (opts: AutocompleteMultiSelectOpti const label = option.label ?? String(option.value ?? ''); const hint = option.hint && focusedValue !== undefined && option.value === focusedValue - ? color.dim(` (${option.hint})`) + ? styleText('dim', ` (${option.hint})`) : ''; - const checkbox = isSelected ? color.green(S_CHECKBOX_SELECTED) : color.dim(S_CHECKBOX_INACTIVE); + const checkbox = isSelected + ? styleText('green', S_CHECKBOX_SELECTED) + : styleText('dim', S_CHECKBOX_INACTIVE); if (active) { return `${checkbox} ${label}${hint}`; } - return `${checkbox} ${color.dim(label)}`; + return `${checkbox} ${styleText('dim', label)}`; }; // Create text prompt which we'll use as foundation @@ -251,7 +258,7 @@ export const autocompleteMultiselect = (opts: AutocompleteMultiSelectOpti output: opts.output, render() { // Title and symbol - const title = `${color.gray(S_BAR)}\n${symbol(this.state)} ${opts.message}\n`; + const title = `${styleText('gray', S_BAR)}\n${symbol(this.state)} ${opts.message}\n`; // Selection counter const userInput = this.userInput; @@ -261,14 +268,15 @@ export const autocompleteMultiselect = (opts: AutocompleteMultiSelectOpti // Search input display const searchText = this.isNavigating || showPlaceholder - ? color.dim(showPlaceholder ? placeholder : userInput) // Just show plain text when in navigation mode + ? styleText('dim', showPlaceholder ? placeholder : userInput) // Just show plain text when in navigation mode : this.userInputWithCursor; const options = this.options; const matches = this.filteredOptions.length !== options.length - ? color.dim( + ? styleText( + 'dim', ` (${this.filteredOptions.length} match${this.filteredOptions.length === 1 ? '' : 'es'})` ) : ''; @@ -276,28 +284,30 @@ 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}${styleText('gray', S_BAR)} ${styleText('dim', `${this.selectedValues.length} items selected`)}`; } case 'cancel': { - return `${title}${color.gray(S_BAR)} ${color.strikethrough(color.dim(userInput))}`; + return `${title}${styleText('gray', S_BAR)} ${styleText('strikethrough', styleText('dim', userInput))}`; } default: { // Instructions const instructions = [ - `${color.dim('↑/↓')} to navigate`, - `${color.dim(this.isNavigating ? 'Space/Tab:' : 'Tab:')} select`, - `${color.dim('Enter:')} confirm`, - `${color.dim('Type:')} to search`, + `${styleText('dim', '↑/↓')} to navigate`, + `${styleText('dim', this.isNavigating ? 'Space/Tab:' : 'Tab:')} select`, + `${styleText('dim', 'Enter:')} confirm`, + `${styleText('dim', 'Type:')} to search`, ]; // No results message const noResults = this.filteredOptions.length === 0 && userInput - ? [`${color.cyan(S_BAR)} ${color.yellow('No matches found')}`] + ? [`${styleText('cyan', S_BAR)} ${styleText('yellow', 'No matches found')}`] : []; const errorMessage = - this.state === 'error' ? [`${color.cyan(S_BAR)} ${color.yellow(this.error)}`] : []; + this.state === 'error' + ? [`${styleText('cyan', S_BAR)} ${styleText('yellow', this.error)}`] + : []; // Get limited options for display const displayOptions = limitOptions({ @@ -312,12 +322,12 @@ export const autocompleteMultiselect = (opts: AutocompleteMultiSelectOpti // Build the prompt display return [ title, - `${color.cyan(S_BAR)} ${color.dim('Search:')} ${searchText}${matches}`, + `${styleText('cyan', S_BAR)} ${styleText('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) => `${styleText('cyan', S_BAR)} ${option}`), + `${styleText('cyan', S_BAR)} ${styleText('dim', instructions.join(' • '))}`, + `${styleText('cyan', S_BAR_END)}`, ].join('\n'); } } diff --git a/packages/prompts/src/common.ts b/packages/prompts/src/common.ts index 57670ab3..33dac766 100644 --- a/packages/prompts/src/common.ts +++ b/packages/prompts/src/common.ts @@ -1,7 +1,7 @@ import type { Readable, Writable } from 'node:stream'; +import { styleText } from 'node:util'; import type { State } from '@clack/core'; import isUnicodeSupported from 'is-unicode-supported'; -import color from 'picocolors'; export const unicode = isUnicodeSupported(); export const isCI = (): boolean => process.env.CI === 'true'; @@ -43,13 +43,13 @@ export const symbol = (state: State) => { switch (state) { case 'initial': case 'active': - return color.cyan(S_STEP_ACTIVE); + return styleText('cyan', S_STEP_ACTIVE); case 'cancel': - return color.red(S_STEP_CANCEL); + return styleText('red', S_STEP_CANCEL); case 'error': - return color.yellow(S_STEP_ERROR); + return styleText('yellow', S_STEP_ERROR); case 'submit': - return color.green(S_STEP_SUBMIT); + return styleText('green', S_STEP_SUBMIT); } }; diff --git a/packages/prompts/src/confirm.ts b/packages/prompts/src/confirm.ts index 4ee30acc..535a59fa 100644 --- a/packages/prompts/src/confirm.ts +++ b/packages/prompts/src/confirm.ts @@ -1,5 +1,5 @@ +import { styleText } from 'node:util'; import { ConfirmPrompt } from '@clack/core'; -import color from 'picocolors'; import { type CommonOptions, S_BAR, @@ -26,26 +26,27 @@ 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 title = `${styleText('gray', S_BAR)}\n${symbol(this.state)} ${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}${styleText('gray', S_BAR)} ${styleText('dim', value)}`; case 'cancel': - return `${title}${color.gray(S_BAR)} ${color.strikethrough( - color.dim(value) - )}\n${color.gray(S_BAR)}`; + return `${title}${styleText('gray', S_BAR)} ${styleText( + 'strikethrough', + styleText('dim', value) + )}\n${styleText('gray', S_BAR)}`; default: { - return `${title}${color.cyan(S_BAR)} ${ + return `${title}${styleText('cyan', S_BAR)} ${ this.value - ? `${color.green(S_RADIO_ACTIVE)} ${active}` - : `${color.dim(S_RADIO_INACTIVE)} ${color.dim(active)}` - } ${color.dim('/')} ${ + ? `${styleText('green', S_RADIO_ACTIVE)} ${active}` + : `${styleText('dim', S_RADIO_INACTIVE)} ${styleText('dim', active)}` + } ${styleText('dim', '/')} ${ !this.value - ? `${color.green(S_RADIO_ACTIVE)} ${inactive}` - : `${color.dim(S_RADIO_INACTIVE)} ${color.dim(inactive)}` - }\n${color.cyan(S_BAR_END)}\n`; + ? `${styleText('green', S_RADIO_ACTIVE)} ${inactive}` + : `${styleText('dim', S_RADIO_INACTIVE)} ${styleText('dim', inactive)}` + }\n${styleText('cyan', S_BAR_END)}\n`; } } }, diff --git a/packages/prompts/src/group-multi-select.ts b/packages/prompts/src/group-multi-select.ts index 4464b074..dd5ea304 100644 --- a/packages/prompts/src/group-multi-select.ts +++ b/packages/prompts/src/group-multi-select.ts @@ -1,5 +1,7 @@ +// import color from 'picocolors'; +import { styleText } from 'node:util'; import { GroupMultiSelectPrompt } from '@clack/core'; -import color from 'picocolors'; + import { type CommonOptions, S_BAR, @@ -42,40 +44,42 @@ 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${styleText('cyan', S_BAR)}`; spacingPrefix = `${spacingPrefixText.repeat(groupSpacing - 1)}${spacingPrefixText} `; } if (state === 'active') { - return `${spacingPrefix}${color.dim(prefix)}${color.cyan(S_CHECKBOX_ACTIVE)} ${label}${ - option.hint ? ` ${color.dim(`(${option.hint})`)}` : '' + return `${spacingPrefix}${styleText('dim', prefix)}${styleText('cyan', S_CHECKBOX_ACTIVE)} ${label}${ + option.hint ? ` ${styleText('dim', `(${option.hint})`)}` : '' }`; } if (state === 'group-active') { - return `${spacingPrefix}${prefix}${color.cyan(S_CHECKBOX_ACTIVE)} ${color.dim(label)}`; + return `${spacingPrefix}${prefix}${styleText('cyan', S_CHECKBOX_ACTIVE)} ${styleText('dim', label)}`; } if (state === 'group-active-selected') { - return `${spacingPrefix}${prefix}${color.green(S_CHECKBOX_SELECTED)} ${color.dim(label)}`; + return `${spacingPrefix}${prefix}${styleText('green', S_CHECKBOX_SELECTED)} ${styleText('dim', label)}`; } if (state === 'selected') { - const selectedCheckbox = isItem || selectableGroups ? color.green(S_CHECKBOX_SELECTED) : ''; - return `${spacingPrefix}${color.dim(prefix)}${selectedCheckbox} ${color.dim(label)}${ - option.hint ? ` ${color.dim(`(${option.hint})`)}` : '' + const selectedCheckbox = + isItem || selectableGroups ? styleText('green', S_CHECKBOX_SELECTED) : ''; + return `${spacingPrefix}${styleText('dim', prefix)}${selectedCheckbox} ${styleText('dim', label)}${ + option.hint ? ` ${styleText('dim', `(${option.hint})`)}` : '' }`; } if (state === 'cancelled') { - return `${color.strikethrough(color.dim(label))}`; + return `${styleText('strikethrough', styleText('dim', label))}`; } if (state === 'active-selected') { - return `${spacingPrefix}${color.dim(prefix)}${color.green(S_CHECKBOX_SELECTED)} ${label}${ - option.hint ? ` ${color.dim(`(${option.hint})`)}` : '' + return `${spacingPrefix}${styleText('dim', prefix)}${styleText('green', S_CHECKBOX_SELECTED)} ${label}${ + option.hint ? ` ${styleText('dim', `(${option.hint})`)}` : '' }`; } if (state === 'submitted') { - return `${color.dim(label)}`; + return `${styleText('dim', label)}`; } - const unselectedCheckbox = isItem || selectableGroups ? color.dim(S_CHECKBOX_INACTIVE) : ''; - return `${spacingPrefix}${color.dim(prefix)}${unselectedCheckbox} ${color.dim(label)}`; + const unselectedCheckbox = + isItem || selectableGroups ? styleText('dim', S_CHECKBOX_INACTIVE) : ''; + return `${spacingPrefix}${styleText('dim', prefix)}${unselectedCheckbox} ${styleText('dim', label)}`; }; const required = opts.required ?? true; @@ -90,16 +94,19 @@ export const groupMultiselect = (opts: GroupMultiSelectOptions) => selectableGroups, validate(selected: Value[] | undefined) { if (required && (selected === undefined || selected.length === 0)) - return `Please select at least one option.\n${color.reset( - color.dim( - `Press ${color.gray(color.bgWhite(color.inverse(' space ')))} to select, ${color.gray( - color.bgWhite(color.inverse(' enter ')) + return `Please select at least one option.\n${styleText( + 'reset', + styleText( + 'dim', + `Press ${styleText('gray', styleText('bgWhite', styleText('inverse', ' space ')))} to select, ${styleText( + 'gray', + styleText('bgWhite', styleText('inverse', ' enter ')) )} to submit` ) )}`; }, render() { - const title = `${color.gray(S_BAR)}\n${symbol(this.state)} ${opts.message}\n`; + const title = `${styleText('gray', S_BAR)}\n${symbol(this.state)} ${opts.message}\n`; const value = this.value ?? []; switch (this.state) { @@ -108,26 +115,26 @@ export const groupMultiselect = (opts: GroupMultiSelectOptions) => .filter(({ value: optionValue }) => value.includes(optionValue)) .map((option) => opt(option, 'submitted')); const optionsText = - selectedOptions.length === 0 ? '' : ` ${selectedOptions.join(color.dim(', '))}`; - return `${title}${color.gray(S_BAR)}${optionsText}`; + selectedOptions.length === 0 ? '' : ` ${selectedOptions.join(styleText('dim', ', '))}`; + return `${title}${styleText('gray', S_BAR)}${optionsText}`; } case 'cancel': { const label = this.options .filter(({ value: optionValue }) => value.includes(optionValue)) .map((option) => opt(option, 'cancelled')) - .join(color.dim(', ')); - return `${title}${color.gray(S_BAR)} ${ - label.trim() ? `${label}\n${color.gray(S_BAR)}` : '' + .join(styleText('dim', ', ')); + return `${title}${styleText('gray', S_BAR)} ${ + label.trim() ? `${label}\n${styleText('gray', S_BAR)}` : '' }`; } case 'error': { const footer = this.error .split('\n') .map((ln, i) => - i === 0 ? `${color.yellow(S_BAR_END)} ${color.yellow(ln)}` : ` ${ln}` + i === 0 ? `${styleText('yellow', S_BAR_END)} ${styleText('yellow', ln)}` : ` ${ln}` ) .join('\n'); - return `${title}${color.yellow(S_BAR)} ${this.options + return `${title}${styleText('yellow', S_BAR)} ${this.options .map((option, i, options) => { const selected = value.includes(option.value) || @@ -148,7 +155,7 @@ export const groupMultiselect = (opts: GroupMultiSelectOptions) => } return opt(option, active ? 'active' : 'inactive', options); }) - .join(`\n${color.yellow(S_BAR)} `)}\n${footer}\n`; + .join(`\n${styleText('yellow', S_BAR)} `)}\n${footer}\n`; } default: { const optionsText = this.options @@ -178,9 +185,9 @@ export const groupMultiselect = (opts: GroupMultiSelectOptions) => const prefix = i !== 0 && !optionText.startsWith('\n') ? ' ' : ''; return `${prefix}${optionText}`; }) - .join(`\n${color.cyan(S_BAR)}`); + .join(`\n${styleText('cyan', S_BAR)}`); const optionsPrefix = optionsText.startsWith('\n') ? '' : ' '; - return `${title}${color.cyan(S_BAR)}${optionsPrefix}${optionsText}\n${color.cyan(S_BAR_END)}\n`; + return `${title}${styleText('cyan', S_BAR)}${optionsPrefix}${optionsText}\n${styleText('cyan', S_BAR_END)}\n`; } } }, diff --git a/packages/prompts/src/limit-options.ts b/packages/prompts/src/limit-options.ts index 11d8f6d1..dcc10467 100644 --- a/packages/prompts/src/limit-options.ts +++ b/packages/prompts/src/limit-options.ts @@ -1,7 +1,7 @@ import type { Writable } from 'node:stream'; +import { styleText } from 'node:util'; import { getColumns, getRows } from '@clack/core'; import { wrapAnsi } from 'fast-wrap-ansi'; -import color from 'picocolors'; import type { CommonOptions } from './common.js'; export interface LimitOptionsParams extends CommonOptions { @@ -41,7 +41,7 @@ export const limitOptions = (params: LimitOptionsParams): stri const rowPadding = params.rowPadding ?? 4; const maxWidth = columns - columnPadding; const rows = getRows(output); - const overflowFormat = color.dim('...'); + const overflowFormat = styleText('dim', '...'); const paramMaxItems = params.maxItems ?? Number.POSITIVE_INFINITY; const outputMaxItems = Math.max(rows - rowPadding, 0); diff --git a/packages/prompts/src/log.ts b/packages/prompts/src/log.ts index ff4e00cd..431fc0a8 100644 --- a/packages/prompts/src/log.ts +++ b/packages/prompts/src/log.ts @@ -1,4 +1,4 @@ -import color from 'picocolors'; +import { styleText } from 'node:util'; import { type CommonOptions, S_BAR, @@ -19,8 +19,8 @@ export const log = { message: ( message: string | string[] = [], { - symbol = color.gray(S_BAR), - secondarySymbol = color.gray(S_BAR), + symbol = styleText('gray', S_BAR), + secondarySymbol = styleText('gray', S_BAR), output = process.stdout, spacing = 1, }: LogMessageOptions = {} @@ -48,22 +48,22 @@ export const log = { output.write(`${parts.join('\n')}\n`); }, info: (message: string, opts?: LogMessageOptions) => { - log.message(message, { ...opts, symbol: color.blue(S_INFO) }); + log.message(message, { ...opts, symbol: styleText('blue', S_INFO) }); }, success: (message: string, opts?: LogMessageOptions) => { - log.message(message, { ...opts, symbol: color.green(S_SUCCESS) }); + log.message(message, { ...opts, symbol: styleText('green', S_SUCCESS) }); }, step: (message: string, opts?: LogMessageOptions) => { - log.message(message, { ...opts, symbol: color.green(S_STEP_SUBMIT) }); + log.message(message, { ...opts, symbol: styleText('green', S_STEP_SUBMIT) }); }, warn: (message: string, opts?: LogMessageOptions) => { - log.message(message, { ...opts, symbol: color.yellow(S_WARN) }); + log.message(message, { ...opts, symbol: styleText('yellow', S_WARN) }); }, /** alias for `log.warn()`. */ warning: (message: string, opts?: LogMessageOptions) => { log.warn(message, opts); }, error: (message: string, opts?: LogMessageOptions) => { - log.message(message, { ...opts, symbol: color.red(S_ERROR) }); + log.message(message, { ...opts, symbol: styleText('red', S_ERROR) }); }, }; diff --git a/packages/prompts/src/messages.ts b/packages/prompts/src/messages.ts index db96d46e..aff5f422 100644 --- a/packages/prompts/src/messages.ts +++ b/packages/prompts/src/messages.ts @@ -1,18 +1,18 @@ import type { Writable } from 'node:stream'; -import color from 'picocolors'; +import { styleText } from 'node:util'; import { type CommonOptions, S_BAR, S_BAR_END, S_BAR_START } from './common.js'; export const cancel = (message = '', opts?: CommonOptions) => { const output: Writable = opts?.output ?? process.stdout; - output.write(`${color.gray(S_BAR_END)} ${color.red(message)}\n\n`); + output.write(`${styleText('gray', S_BAR_END)} ${styleText('red', message)}\n\n`); }; export const intro = (title = '', opts?: CommonOptions) => { const output: Writable = opts?.output ?? process.stdout; - output.write(`${color.gray(S_BAR_START)} ${title}\n`); + output.write(`${styleText('gray', S_BAR_START)} ${title}\n`); }; export const outro = (message = '', opts?: CommonOptions) => { const output: Writable = opts?.output ?? process.stdout; - output.write(`${color.gray(S_BAR)}\n${color.gray(S_BAR_END)} ${message}\n\n`); + output.write(`${styleText('gray', S_BAR)}\n${styleText('gray', S_BAR_END)} ${message}\n\n`); }; diff --git a/packages/prompts/src/multi-select.ts b/packages/prompts/src/multi-select.ts index 1d928a7e..705eb682 100644 --- a/packages/prompts/src/multi-select.ts +++ b/packages/prompts/src/multi-select.ts @@ -1,5 +1,5 @@ +import { styleText } from 'node:util'; import { MultiSelectPrompt } from '@clack/core'; -import color from 'picocolors'; import { type CommonOptions, S_BAR, @@ -27,27 +27,27 @@ export const multiselect = (opts: MultiSelectOptions) => { ) => { const label = option.label ?? String(option.value); if (state === 'active') { - return `${color.cyan(S_CHECKBOX_ACTIVE)} ${label}${ - option.hint ? ` ${color.dim(`(${option.hint})`)}` : '' + return `${styleText('cyan', S_CHECKBOX_ACTIVE)} ${label}${ + option.hint ? ` ${styleText('dim', `(${option.hint})`)}` : '' }`; } if (state === 'selected') { - return `${color.green(S_CHECKBOX_SELECTED)} ${color.dim(label)}${ - option.hint ? ` ${color.dim(`(${option.hint})`)}` : '' + return `${styleText('green', S_CHECKBOX_SELECTED)} ${styleText('dim', label)}${ + option.hint ? ` ${styleText('dim', `(${option.hint})`)}` : '' }`; } if (state === 'cancelled') { - return `${color.strikethrough(color.dim(label))}`; + return `${styleText('strikethrough', styleText('dim', label))}`; } if (state === 'active-selected') { - return `${color.green(S_CHECKBOX_SELECTED)} ${label}${ - option.hint ? ` ${color.dim(`(${option.hint})`)}` : '' + return `${styleText('green', S_CHECKBOX_SELECTED)} ${label}${ + option.hint ? ` ${styleText('dim', `(${option.hint})`)}` : '' }`; } if (state === 'submitted') { - return `${color.dim(label)}`; + return `${styleText('dim', label)}`; } - return `${color.dim(S_CHECKBOX_INACTIVE)} ${color.dim(label)}`; + return `${styleText('dim', S_CHECKBOX_INACTIVE)} ${styleText('dim', label)}`; }; const required = opts.required ?? true; @@ -61,16 +61,19 @@ export const multiselect = (opts: MultiSelectOptions) => { cursorAt: opts.cursorAt, validate(selected: Value[] | undefined) { if (required && (selected === undefined || selected.length === 0)) - return `Please select at least one option.\n${color.reset( - color.dim( - `Press ${color.gray(color.bgWhite(color.inverse(' space ')))} to select, ${color.gray( - color.bgWhite(color.inverse(' enter ')) + return `Please select at least one option.\n${styleText( + 'reset', + styleText( + 'dim', + `Press ${styleText('gray', styleText('bgWhite', styleText('inverse', ' space ')))} to select, ${styleText( + 'gray', + styleText('bgWhite', styleText('inverse', ' enter ')) )} to submit` ) )}`; }, render() { - const title = `${color.gray(S_BAR)}\n${symbol(this.state)} ${opts.message}\n`; + const title = `${styleText('gray', S_BAR)}\n${symbol(this.state)} ${opts.message}\n`; const value = this.value ?? []; const styleOption = (option: Option, active: boolean) => { @@ -86,45 +89,45 @@ export const multiselect = (opts: MultiSelectOptions) => { switch (this.state) { case 'submit': { - return `${title}${color.gray(S_BAR)} ${ + return `${title}${styleText('gray', S_BAR)} ${ this.options .filter(({ value: optionValue }) => value.includes(optionValue)) .map((option) => opt(option, 'submitted')) - .join(color.dim(', ')) || color.dim('none') + .join(styleText('dim', ', ')) || styleText('dim', 'none') }`; } case 'cancel': { const label = this.options .filter(({ value: optionValue }) => value.includes(optionValue)) .map((option) => opt(option, 'cancelled')) - .join(color.dim(', ')); - return `${title}${color.gray(S_BAR)}${ - label.trim() ? ` ${label}\n${color.gray(S_BAR)}` : '' + .join(styleText('dim', ', ')); + return `${title}${styleText('gray', S_BAR)}${ + label.trim() ? ` ${label}\n${styleText('gray', S_BAR)}` : '' }`; } case 'error': { const footer = this.error .split('\n') .map((ln, i) => - i === 0 ? `${color.yellow(S_BAR_END)} ${color.yellow(ln)}` : ` ${ln}` + i === 0 ? `${styleText('yellow', S_BAR_END)} ${styleText('yellow', ln)}` : ` ${ln}` ) .join('\n'); - return `${title + color.yellow(S_BAR)} ${limitOptions({ + return `${title + styleText('yellow', S_BAR)} ${limitOptions({ output: opts.output, options: this.options, cursor: this.cursor, maxItems: opts.maxItems, style: styleOption, - }).join(`\n${color.yellow(S_BAR)} `)}\n${footer}\n`; + }).join(`\n${styleText('yellow', S_BAR)} `)}\n${footer}\n`; } default: { - return `${title}${color.cyan(S_BAR)} ${limitOptions({ + return `${title}${styleText('cyan', S_BAR)} ${limitOptions({ output: opts.output, options: this.options, cursor: this.cursor, maxItems: opts.maxItems, style: styleOption, - }).join(`\n${color.cyan(S_BAR)} `)}\n${color.cyan(S_BAR_END)}\n`; + }).join(`\n${styleText('cyan', S_BAR)} `)}\n${styleText('cyan', S_BAR_END)}\n`; } } }, diff --git a/packages/prompts/src/note.ts b/packages/prompts/src/note.ts index bd80d07b..50f7b0e9 100644 --- a/packages/prompts/src/note.ts +++ b/packages/prompts/src/note.ts @@ -1,9 +1,9 @@ import process from 'node:process'; import type { Writable } from 'node:stream'; +import { styleText } from 'node:util'; import { getColumns } from '@clack/core'; import stringWidth from 'fast-string-width'; import { type Options as WrapAnsiOptions, wrapAnsi } from 'fast-wrap-ansi'; -import color from 'picocolors'; import { type CommonOptions, S_BAR, @@ -19,7 +19,7 @@ export interface NoteOptions extends CommonOptions { format?: FormatFn; } -const defaultNoteFormatter = (line: string): string => color.dim(line); +const defaultNoteFormatter = (line: string): string => styleText('dim', line); const wrapWithFormat = (message: string, width: number, format: FormatFn): string => { const opts: WrapAnsiOptions = { @@ -49,12 +49,14 @@ export const note = (message = '', title = '', opts?: NoteOptions) => { ) + 2; const msg = lines .map( - (ln) => `${color.gray(S_BAR)} ${ln}${' '.repeat(len - stringWidth(ln))}${color.gray(S_BAR)}` + (ln) => + `${styleText('gray', S_BAR)} ${ln}${' '.repeat(len - stringWidth(ln))}${styleText('gray', S_BAR)}` ) .join('\n'); output.write( - `${color.gray(S_BAR)}\n${color.green(S_STEP_SUBMIT)} ${color.reset(title)} ${color.gray( + `${styleText('gray', S_BAR)}\n${styleText('green', S_STEP_SUBMIT)} ${styleText('reset', title)} ${styleText( + 'gray', S_BAR_H.repeat(Math.max(len - titleLen - 1, 1)) + S_CORNER_TOP_RIGHT - )}\n${msg}\n${color.gray(S_CONNECT_LEFT + S_BAR_H.repeat(len + 2) + S_CORNER_BOTTOM_RIGHT)}\n` + )}\n${msg}\n${styleText('gray', S_CONNECT_LEFT + S_BAR_H.repeat(len + 2) + S_CORNER_BOTTOM_RIGHT)}\n` ); }; diff --git a/packages/prompts/src/password.ts b/packages/prompts/src/password.ts index 8010960b..3516f73c 100644 --- a/packages/prompts/src/password.ts +++ b/packages/prompts/src/password.ts @@ -1,5 +1,5 @@ +import { styleText } from 'node:util'; import { PasswordPrompt } from '@clack/core'; -import color from 'picocolors'; import { type CommonOptions, S_BAR, S_BAR_END, S_PASSWORD_MASK, symbol } from './common.js'; export interface PasswordOptions extends CommonOptions { @@ -16,7 +16,8 @@ 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 title = `${color.gray(S_BAR)}\n${symbol(this.state)} ${opts.message}\n`; + const title = `${styleText('gray', S_BAR)}\n${symbol(this.state)} ${opts.message}\n`; const userInput = this.userInputWithCursor; const masked = this.masked; @@ -26,22 +27,25 @@ export const password = (opts: PasswordOptions) => { if (opts.clearOnError) { this.clear(); } - return `${title.trim()}\n${color.yellow(S_BAR)}${maskedText}\n${color.yellow( + return `${title.trim()}\n${styleText('yellow', S_BAR)}${maskedText}\n${styleText( + 'yellow', S_BAR_END - )} ${color.yellow(this.error)}\n`; + )} ${styleText('yellow', this.error)}\n`; } case 'submit': { - const maskedText = masked ? ` ${color.dim(masked)}` : ''; - return `${title}${color.gray(S_BAR)}${maskedText}`; + const maskedText = masked ? ` ${styleText('dim', masked)}` : ''; + return `${title}${styleText('gray', S_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)}` : '' + const maskedText = masked + ? ` ${styleText('strikethrough', styleText('dim', masked))}` + : ''; + return `${title}${styleText('gray', S_BAR)}${maskedText}${ + masked ? `\n${styleText('gray', S_BAR)}` : '' }`; } default: - return `${title}${color.cyan(S_BAR)} ${userInput}\n${color.cyan(S_BAR_END)}\n`; + return `${title}${styleText('cyan', S_BAR)} ${userInput}\n${styleText('cyan', S_BAR_END)}\n`; } }, }).prompt() as Promise; diff --git a/packages/prompts/src/progress-bar.ts b/packages/prompts/src/progress-bar.ts index 8801833c..2a22de7b 100644 --- a/packages/prompts/src/progress-bar.ts +++ b/packages/prompts/src/progress-bar.ts @@ -1,5 +1,5 @@ +import { styleText } from 'node:util'; import type { State } from '@clack/core'; -import color from 'picocolors'; import { unicodeOr } from './common.js'; import { type SpinnerOptions, type SpinnerResult, spinner } from './spinner.js'; @@ -36,19 +36,19 @@ export function progress({ switch (state) { case 'initial': case 'active': - return color.magenta; + return (text: string) => styleText('magenta', text); case 'error': case 'cancel': - return color.red; + return (text: string) => styleText('red', text); case 'submit': - return color.green; + return (text: string) => styleText('green', text); default: - return color.magenta; + return (text: string) => styleText('magenta', text); } }; const drawProgress = (state: State, msg: string) => { const active = Math.floor((value / max) * size); - return `${activeStyle(state)(S_PROGRESS_CHAR[style].repeat(active))}${color.dim(S_PROGRESS_CHAR[style].repeat(size - active))} ${msg}`; + return `${activeStyle(state)(S_PROGRESS_CHAR[style].repeat(active))}${styleText('dim', S_PROGRESS_CHAR[style].repeat(size - active))} ${msg}`; }; const start = (msg = '') => { diff --git a/packages/prompts/src/select-key.ts b/packages/prompts/src/select-key.ts index f5bbbf69..6d53ddd2 100644 --- a/packages/prompts/src/select-key.ts +++ b/packages/prompts/src/select-key.ts @@ -1,5 +1,5 @@ +import { styleText } from 'node:util'; import { SelectKeyPrompt } from '@clack/core'; -import color from 'picocolors'; import { S_BAR, S_BAR_END, symbol } from './common.js'; import type { Option, SelectOptions } from './select.js'; @@ -10,18 +10,18 @@ export const selectKey = (opts: SelectOptions) => { ) => { const label = option.label ?? String(option.value); if (state === 'selected') { - return `${color.dim(label)}`; + return `${styleText('dim', label)}`; } if (state === 'cancelled') { - return `${color.strikethrough(color.dim(label))}`; + return `${styleText('strikethrough', styleText('dim', label))}`; } if (state === 'active') { - return `${color.bgCyan(color.gray(` ${option.value} `))} ${label} ${ - option.hint ? color.dim(`(${option.hint})`) : '' + return `${styleText('bgCyan', styleText('gray', ` ${option.value} `))} ${label} ${ + option.hint ? styleText('dim', `(${option.hint})`) : '' }`; } - return `${color.gray(color.bgWhite(color.inverse(` ${option.value} `)))} ${label} ${ - option.hint ? color.dim(`(${option.hint})`) : '' + return `${styleText('gray', styleText('bgWhite', styleText('inverse', ` ${option.value} `)))} ${label} ${ + option.hint ? styleText('dim', `(${option.hint})`) : '' }`; }; @@ -32,22 +32,23 @@ 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 title = `${styleText('gray', S_BAR)}\n${symbol(this.state)} ${opts.message}\n`; switch (this.state) { case 'submit': - return `${title}${color.gray(S_BAR)} ${opt( + return `${title}${styleText('gray', S_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( + return `${title}${styleText('gray', S_BAR)} ${opt(this.options[0], 'cancelled')}\n${styleText( + 'gray', S_BAR )}`; default: { - return `${title}${color.cyan(S_BAR)} ${this.options + return `${title}${styleText('cyan', S_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${styleText('cyan', S_BAR)} `)}\n${styleText('cyan', S_BAR_END)}\n`; } } }, diff --git a/packages/prompts/src/select.ts b/packages/prompts/src/select.ts index 1dafaea3..4515b644 100644 --- a/packages/prompts/src/select.ts +++ b/packages/prompts/src/select.ts @@ -1,5 +1,5 @@ +import { styleText } from 'node:util'; import { SelectPrompt } from '@clack/core'; -import color from 'picocolors'; import { type CommonOptions, S_BAR, @@ -62,15 +62,15 @@ export const select = (opts: SelectOptions) => { const label = option.label ?? String(option.value); switch (state) { case 'selected': - return `${color.dim(label)}`; + return `${styleText('dim', label)}`; case 'active': - return `${color.green(S_RADIO_ACTIVE)} ${label}${ - option.hint ? ` ${color.dim(`(${option.hint})`)}` : '' + return `${styleText('green', S_RADIO_ACTIVE)} ${label}${ + option.hint ? ` ${styleText('dim', `(${option.hint})`)}` : '' }`; case 'cancelled': - return `${color.strikethrough(color.dim(label))}`; + return `${styleText('strikethrough', styleText('dim', label))}`; default: - return `${color.dim(S_RADIO_INACTIVE)} ${color.dim(label)}`; + return `${styleText('dim', S_RADIO_INACTIVE)} ${styleText('dim', label)}`; } }; @@ -81,24 +81,24 @@ export const select = (opts: SelectOptions) => { output: opts.output, initialValue: opts.initialValue, render() { - const title = `${color.gray(S_BAR)}\n${symbol(this.state)} ${opts.message}\n`; + const title = `${styleText('gray', S_BAR)}\n${symbol(this.state)} ${opts.message}\n`; switch (this.state) { case 'submit': - return `${title}${color.gray(S_BAR)} ${opt(this.options[this.cursor], 'selected')}`; + return `${title}${styleText('gray', S_BAR)} ${opt(this.options[this.cursor], 'selected')}`; case 'cancel': - return `${title}${color.gray(S_BAR)} ${opt( + return `${title}${styleText('gray', S_BAR)} ${opt( this.options[this.cursor], 'cancelled' - )}\n${color.gray(S_BAR)}`; + )}\n${styleText('gray', S_BAR)}`; default: { - return `${title}${color.cyan(S_BAR)} ${limitOptions({ + return `${title}${styleText('cyan', S_BAR)} ${limitOptions({ output: opts.output, cursor: this.cursor, options: this.options, maxItems: opts.maxItems, style: (item, active) => opt(item, active ? 'active' : 'inactive'), - }).join(`\n${color.cyan(S_BAR)} `)}\n${color.cyan(S_BAR_END)}\n`; + }).join(`\n${styleText('cyan', S_BAR)} `)}\n${styleText('cyan', S_BAR_END)}\n`; } } }, diff --git a/packages/prompts/src/spinner.ts b/packages/prompts/src/spinner.ts index 7b4c8cec..071e2755 100644 --- a/packages/prompts/src/spinner.ts +++ b/packages/prompts/src/spinner.ts @@ -1,6 +1,6 @@ +import { styleText } from 'node:util'; import { block, getColumns, settings } from '@clack/core'; import { wrapAnsi } from 'fast-wrap-ansi'; -import color from 'picocolors'; import { cursor, erase } from 'sisteransi'; import { type CommonOptions, @@ -124,7 +124,7 @@ export const spinner = ({ unblock = block({ output }); _message = removeTrailingDots(msg); _origin = performance.now(); - output.write(`${color.gray(S_BAR)}\n`); + output.write(`${styleText('gray', S_BAR)}\n`); let frameIndex = 0; let indicatorTimer = 0; registerHooks(); @@ -134,7 +134,7 @@ export const spinner = ({ } clearPrevMessage(); _prevMessage = _message; - const frame = color.magenta(frames[frameIndex]); + const frame = styleText('magenta', frames[frameIndex]); let outputMessage: string; if (isCI) { @@ -165,10 +165,10 @@ export const spinner = ({ clearPrevMessage(); const step = code === 0 - ? color.green(S_STEP_SUBMIT) + ? styleText('green', S_STEP_SUBMIT) : code === 1 - ? color.red(S_STEP_CANCEL) - : color.red(S_STEP_ERROR); + ? styleText('red', S_STEP_CANCEL) + : styleText('red', S_STEP_ERROR); _message = msg ?? _message; if (indicator === 'timer') { output.write(`${step} ${_message} ${formatTimer(_origin)}\n`); diff --git a/packages/prompts/src/stream.ts b/packages/prompts/src/stream.ts index 19e79c11..4ff3db5e 100644 --- a/packages/prompts/src/stream.ts +++ b/packages/prompts/src/stream.ts @@ -1,9 +1,8 @@ -import { stripVTControlCharacters as strip } from 'node:util'; -import color from 'picocolors'; +import { stripVTControlCharacters as strip, styleText } from 'node:util'; import { S_BAR, S_ERROR, S_INFO, S_STEP_SUBMIT, S_SUCCESS, S_WARN } from './common.js'; import type { LogMessageOptions } from './log.js'; -const prefix = `${color.gray(S_BAR)} `; +const prefix = `${styleText('gray', S_BAR)} `; // TODO (43081j): this currently doesn't support custom `output` writables // because we rely on `columns` existing (i.e. `process.stdout.columns). @@ -13,9 +12,9 @@ const prefix = `${color.gray(S_BAR)} `; export const stream = { message: async ( iterable: Iterable | AsyncIterable, - { symbol = color.gray(S_BAR) }: LogMessageOptions = {} + { symbol = styleText('gray', S_BAR) }: LogMessageOptions = {} ) => { - process.stdout.write(`${color.gray(S_BAR)}\n${symbol} `); + process.stdout.write(`${styleText('gray', S_BAR)}\n${symbol} `); let lineWidth = 3; for await (let chunk of iterable) { chunk = chunk.replace(/\n/g, `\n${prefix}`); @@ -34,22 +33,22 @@ export const stream = { process.stdout.write('\n'); }, info: (iterable: Iterable | AsyncIterable) => { - return stream.message(iterable, { symbol: color.blue(S_INFO) }); + return stream.message(iterable, { symbol: styleText('blue', S_INFO) }); }, success: (iterable: Iterable | AsyncIterable) => { - return stream.message(iterable, { symbol: color.green(S_SUCCESS) }); + return stream.message(iterable, { symbol: styleText('green', S_SUCCESS) }); }, step: (iterable: Iterable | AsyncIterable) => { - return stream.message(iterable, { symbol: color.green(S_STEP_SUBMIT) }); + return stream.message(iterable, { symbol: styleText('green', S_STEP_SUBMIT) }); }, warn: (iterable: Iterable | AsyncIterable) => { - return stream.message(iterable, { symbol: color.yellow(S_WARN) }); + return stream.message(iterable, { symbol: styleText('yellow', S_WARN) }); }, /** alias for `log.warn()`. */ warning: (iterable: Iterable | AsyncIterable) => { return stream.warn(iterable); }, error: (iterable: Iterable | AsyncIterable) => { - return stream.message(iterable, { symbol: color.red(S_ERROR) }); + return stream.message(iterable, { symbol: styleText('red', S_ERROR) }); }, }; diff --git a/packages/prompts/src/task-log.ts b/packages/prompts/src/task-log.ts index a484499f..6b7d78c6 100644 --- a/packages/prompts/src/task-log.ts +++ b/packages/prompts/src/task-log.ts @@ -1,6 +1,6 @@ import type { Writable } from 'node:stream'; +import { styleText } from 'node:util'; import { getColumns } from '@clack/core'; -import color from 'picocolors'; import { erase } from 'sisteransi'; import { type CommonOptions, @@ -42,14 +42,14 @@ interface BufferEntry { export const taskLog = (opts: TaskLogOptions) => { const output: Writable = opts.output ?? process.stdout; const columns = getColumns(output); - const secondarySymbol = color.gray(S_BAR); + const secondarySymbol = styleText('gray', S_BAR); const spacing = opts.spacing ?? 1; const barSize = 3; const retainLog = opts.retainLog === true; const isTTY = !isCIFn() && isTTYFn(output); output.write(`${secondarySymbol}\n`); - output.write(`${color.green(S_STEP_SUBMIT)} ${opts.title}\n`); + output.write(`${styleText('green', S_STEP_SUBMIT)} ${opts.title}\n`); for (let i = 0; i < spacing; i++) { output.write(`${secondarySymbol}\n`); } @@ -103,19 +103,25 @@ export const taskLog = (opts: TaskLogOptions) => { const printBuffer = (buffer: BufferEntry, messageSpacing?: number, full?: boolean): void => { const messages = full ? `${buffer.full}\n${buffer.value}` : buffer.value; if (buffer.header !== undefined && buffer.header !== '') { - log.message(buffer.header.split('\n').map(color.bold), { + log.message( + buffer.header.split('\n').map((line) => styleText('bold', line)), + { + output, + secondarySymbol, + symbol: secondarySymbol, + spacing: 0, + } + ); + } + log.message( + messages.split('\n').map((line) => styleText('dim', line)), + { output, secondarySymbol, symbol: secondarySymbol, - spacing: 0, - }); - } - log.message(messages.split('\n').map(color.dim), { - output, - secondarySymbol, - symbol: secondarySymbol, - spacing: messageSpacing ?? spacing, - }); + spacing: messageSpacing ?? spacing, + } + ); }; const renderBuffer = (): void => { for (const buffer of buffers) { diff --git a/packages/prompts/src/text.ts b/packages/prompts/src/text.ts index 244f7c8d..4a94ff3c 100644 --- a/packages/prompts/src/text.ts +++ b/packages/prompts/src/text.ts @@ -1,5 +1,5 @@ +import { styleText } from 'node:util'; import { TextPrompt } from '@clack/core'; -import color from 'picocolors'; import { type CommonOptions, S_BAR, S_BAR_END, symbol } from './common.js'; export interface TextOptions extends CommonOptions { @@ -20,30 +20,31 @@ 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 title = `${styleText('gray', S_BAR)}\n${symbol(this.state)} ${opts.message}\n`; const placeholder = opts.placeholder - ? color.inverse(opts.placeholder[0]) + color.dim(opts.placeholder.slice(1)) - : color.inverse(color.hidden('_')); + ? styleText('inverse', opts.placeholder[0]) + styleText('dim', opts.placeholder.slice(1)) + : styleText('inverse', styleText('hidden', '_')); const userInput = !this.userInput ? placeholder : this.userInputWithCursor; const value = this.value ?? ''; 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( + const errorText = this.error ? ` ${styleText('yellow', this.error)}` : ''; + return `${title.trim()}\n${styleText('yellow', S_BAR)} ${userInput}\n${styleText( + 'yellow', S_BAR_END )}${errorText}\n`; } case 'submit': { - const valueText = value ? ` ${color.dim(value)}` : ''; - return `${title}${color.gray(S_BAR)}${valueText}`; + const valueText = value ? ` ${styleText('dim', value)}` : ''; + return `${title}${styleText('gray', S_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)}` : ''}`; + const valueText = value ? ` ${styleText('strikethrough', styleText('dim', value))}` : ''; + return `${title}${styleText('gray', S_BAR)}${valueText}${value.trim() ? `\n${styleText('gray', S_BAR)}` : ''}`; } default: - return `${title}${color.cyan(S_BAR)} ${userInput}\n${color.cyan(S_BAR_END)}\n`; + return `${title}${styleText('cyan', S_BAR)} ${userInput}\n${styleText('cyan', S_BAR_END)}\n`; } }, }).prompt() as Promise; diff --git a/packages/prompts/test/box.test.ts b/packages/prompts/test/box.test.ts index 31105984..f3c82532 100644 --- a/packages/prompts/test/box.test.ts +++ b/packages/prompts/test/box.test.ts @@ -1,4 +1,4 @@ -import colors from 'picocolors'; +import { styleText } from 'node:util'; import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test, vi } from 'vitest'; import * as prompts from '../src/index.js'; import { MockReadable, MockWritable } from './test-utils.js'; @@ -229,7 +229,7 @@ describe.each(['true', 'false'])('box (isCI = %s)', (isCI) => { input, output, width: 'auto', - formatBorder: colors.red, + formatBorder: (text: string) => styleText('red', text), }); expect(output.buffer).toMatchSnapshot(); diff --git a/packages/prompts/test/limit-options.test.ts b/packages/prompts/test/limit-options.test.ts index f48f4a7a..aaee854c 100644 --- a/packages/prompts/test/limit-options.test.ts +++ b/packages/prompts/test/limit-options.test.ts @@ -1,4 +1,4 @@ -import color from 'picocolors'; +import { styleText } from 'node:util'; import { beforeEach, describe, expect, test } from 'vitest'; import { type LimitOptionsParams, limitOptions } from '../src/index.js'; import { MockWritable } from './test-utils.js'; @@ -39,7 +39,7 @@ describe('limitOptions', () => { ]; options.maxItems = 3; const result = limitOptions(options); - expect(result).toEqual(['Item 1', 'Item 2', 'Item 3', 'Item 4', color.dim('...')]); + expect(result).toEqual(['Item 1', 'Item 2', 'Item 3', 'Item 4', styleText('dim', '...')]); }); test('returns sliding window when cursor moves down', async () => { @@ -59,7 +59,13 @@ describe('limitOptions', () => { options.maxItems = 5; options.cursor = 6; const result = limitOptions(options); - expect(result).toEqual([color.dim('...'), 'Item 6', 'Item 7', 'Item 8', color.dim('...')]); + expect(result).toEqual([ + styleText('dim', '...'), + 'Item 6', + 'Item 7', + 'Item 8', + styleText('dim', '...'), + ]); }); test('returns sliding window near end of list', async () => { @@ -78,7 +84,7 @@ describe('limitOptions', () => { options.maxItems = 5; options.cursor = 8; const result = limitOptions(options); - expect(result).toEqual([color.dim('...'), 'Item 7', 'Item 8', 'Item 9', 'Item 10']); + expect(result).toEqual([styleText('dim', '...'), 'Item 7', 'Item 8', 'Item 9', 'Item 10']); }); test('handles empty options list', async () => { @@ -103,7 +109,7 @@ describe('limitOptions', () => { output.rows = 7; options.maxItems = 10; const result = limitOptions(options); - expect(result).toEqual(['Item 1', 'Item 2', color.dim('...')]); + expect(result).toEqual(['Item 1', 'Item 2', styleText('dim', '...')]); }); test('handle multi-line item clamping (start)', async () => { @@ -138,7 +144,7 @@ describe('limitOptions', () => { 'Item 6', 'Item 7', 'Item 8', - color.dim('...'), + styleText('dim', '...'), ]); }); @@ -164,7 +170,7 @@ describe('limitOptions', () => { options.cursor = 7; const result = limitOptions(options); expect(result).toEqual([ - color.dim('...'), + styleText('dim', '...'), 'Item 2', 'Item 3', 'Item 4', @@ -175,7 +181,7 @@ describe('limitOptions', () => { 'Item 6', 'Item 7', 'Item 8', - color.dim('...'), + styleText('dim', '...'), ]); }); @@ -201,7 +207,7 @@ describe('limitOptions', () => { options.cursor = 9; const result = limitOptions(options); expect(result).toEqual([ - color.dim('...'), + styleText('dim', '...'), 'Item 4', 'Item 5', 'Item 6', diff --git a/packages/prompts/test/note.test.ts b/packages/prompts/test/note.test.ts index ac0df4fe..887448ed 100644 --- a/packages/prompts/test/note.test.ts +++ b/packages/prompts/test/note.test.ts @@ -1,4 +1,4 @@ -import colors from 'picocolors'; +import { styleText } from 'node:util'; import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test, vi } from 'vitest'; import * as prompts from '../src/index.js'; import { MockReadable, MockWritable } from './test-utils.js'; @@ -56,7 +56,7 @@ describe.each(['true', 'false'])('note (isCI = %s)', (isCI) => { test('formatter which adds colors works', () => { prompts.note('line 0\nline 1\nline 2', 'title', { - format: (line) => colors.red(line), + format: (line) => styleText('red', line), input, output, }); @@ -79,7 +79,7 @@ describe.each(['true', 'false'])('note (isCI = %s)', (isCI) => { const message = `${'test string '.repeat(32)}\n`.repeat(4).trim(); output.columns = 75; prompts.note(message, 'title', { - format: (line) => colors.red(`* ${colors.cyan(line)} *`), + format: (line) => styleText('red', `* ${styleText('cyan', line)} *`), input, output, }); @@ -102,7 +102,7 @@ describe.each(['true', 'false'])('note (isCI = %s)', (isCI) => { const messages = ['이게 첫 번째 줄이에요', 'これは次の行です']; output.columns = 10; prompts.note(messages.join('\n'), '这是标题', { - format: (line) => colors.red(`* ${colors.cyan(line)} *`), + format: (line) => styleText('red', `* ${styleText('cyan', line)} *`), input, output, }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2c19acf1..5d1d962b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -60,9 +60,6 @@ importers: packages/core: dependencies: - picocolors: - specifier: ^1.0.0 - version: 1.1.1 sisteransi: specifier: ^1.0.5 version: 1.0.5 @@ -79,9 +76,6 @@ importers: '@clack/core': specifier: workspace:* version: link:../core - picocolors: - specifier: ^1.0.0 - version: 1.1.1 sisteransi: specifier: ^1.0.5 version: 1.0.5 From fe98515d135b17a4e583d544a04b86ed9a4a1e5a Mon Sep 17 00:00:00 2001 From: Paul Valladares <85648028+dreyfus92@users.noreply.github.com> Date: Wed, 15 Oct 2025 13:16:18 -0500 Subject: [PATCH 2/4] add: changeset --- .changeset/huge-items-throw.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/huge-items-throw.md diff --git a/.changeset/huge-items-throw.md b/.changeset/huge-items-throw.md new file mode 100644 index 00000000..6be63e72 --- /dev/null +++ b/.changeset/huge-items-throw.md @@ -0,0 +1,6 @@ +--- +"@clack/prompts": minor +"@clack/core": minor +--- + +Replaces `picocolors` with Node.js built-in `styleText`. From bb53ac591713929c5af082060ef845060e05438e Mon Sep 17 00:00:00 2001 From: paul valladares <85648028+dreyfus92@users.noreply.github.com> Date: Wed, 15 Oct 2025 19:31:09 -0500 Subject: [PATCH 3/4] Apply suggestions from @43081j's code review. Co-authored-by: James Garbutt <43081j@users.noreply.github.com> --- packages/core/src/prompts/autocomplete.ts | 2 +- packages/core/src/prompts/password.ts | 2 +- packages/core/src/prompts/text.ts | 1 - packages/core/test/prompts/password.test.ts | 4 ++-- packages/prompts/src/autocomplete.ts | 2 +- packages/prompts/src/confirm.ts | 4 ++-- packages/prompts/src/group-multi-select.ts | 7 +++---- packages/prompts/src/multi-select.ts | 2 +- packages/prompts/src/password.ts | 3 +-- packages/prompts/src/select-key.ts | 6 +++--- packages/prompts/src/select.ts | 2 +- packages/prompts/src/text.ts | 4 ++-- 12 files changed, 18 insertions(+), 21 deletions(-) diff --git a/packages/core/src/prompts/autocomplete.ts b/packages/core/src/prompts/autocomplete.ts index c636d92e..91c154ea 100644 --- a/packages/core/src/prompts/autocomplete.ts +++ b/packages/core/src/prompts/autocomplete.ts @@ -71,7 +71,7 @@ export default class AutocompletePrompt extends Prompt< get userInputWithCursor() { if (!this.userInput) { - return styleText('inverse', styleText('hidden', '_')); + return styleText(['inverse', 'hidden'], '_'); } if (this._cursor >= this.userInput.length) { return `${this.userInput}█`; diff --git a/packages/core/src/prompts/password.ts b/packages/core/src/prompts/password.ts index 331b8fa1..d7885407 100644 --- a/packages/core/src/prompts/password.ts +++ b/packages/core/src/prompts/password.ts @@ -18,7 +18,7 @@ export default class PasswordPrompt extends Prompt { } const userInput = this.userInput; if (this.cursor >= userInput.length) { - return `${this.masked}${styleText('inverse', styleText('hidden', '_'))}`; + return `${this.masked}${styleText(['inverse', 'hidden'], '_')}`; } const masked = this.masked; const s1 = masked.slice(0, this.cursor); diff --git a/packages/core/src/prompts/text.ts b/packages/core/src/prompts/text.ts index 2e70bf6b..e4d47840 100644 --- a/packages/core/src/prompts/text.ts +++ b/packages/core/src/prompts/text.ts @@ -1,4 +1,3 @@ -// import color from 'picocolors'; import { styleText } from 'node:util'; import Prompt, { type PromptOptions } from './prompt.js'; diff --git a/packages/core/test/prompts/password.test.ts b/packages/core/test/prompts/password.test.ts index 33d1c584..4847b616 100644 --- a/packages/core/test/prompts/password.test.ts +++ b/packages/core/test/prompts/password.test.ts @@ -66,7 +66,7 @@ describe('PasswordPrompt', () => { instance.prompt(); input.emit('keypress', 'x', { name: 'x' }); expect(instance.userInputWithCursor).to.equal( - `•${styleText('inverse', styleText('hidden', '_'))}` + `•${styleText(['inverse', 'hidden'], '_')}` ); }); @@ -95,7 +95,7 @@ describe('PasswordPrompt', () => { instance.prompt(); input.emit('keypress', 'x', { name: 'x' }); expect(instance.userInputWithCursor).to.equal( - `X${styleText('inverse', styleText('hidden', '_'))}` + `X${styleText(['inverse', 'hidden'], '_')}` ); }); }); diff --git a/packages/prompts/src/autocomplete.ts b/packages/prompts/src/autocomplete.ts index 4ec5fca0..41710157 100644 --- a/packages/prompts/src/autocomplete.ts +++ b/packages/prompts/src/autocomplete.ts @@ -108,7 +108,7 @@ export const autocomplete = (opts: AutocompleteOptions) => { case 'cancel': { const userInputText = userInput - ? ` ${styleText('strikethrough', styleText('dim', userInput))}` + ? ` ${styleText(['strikethrough', 'dim'], userInput)}` : ''; return `${headings.join('\n')}\n${styleText('gray', S_BAR)}${userInputText}`; } diff --git a/packages/prompts/src/confirm.ts b/packages/prompts/src/confirm.ts index 535a59fa..559e7e1a 100644 --- a/packages/prompts/src/confirm.ts +++ b/packages/prompts/src/confirm.ts @@ -34,8 +34,8 @@ export const confirm = (opts: ConfirmOptions) => { return `${title}${styleText('gray', S_BAR)} ${styleText('dim', value)}`; case 'cancel': return `${title}${styleText('gray', S_BAR)} ${styleText( - 'strikethrough', - styleText('dim', value) + ['strikethrough', 'dim'], + value )}\n${styleText('gray', S_BAR)}`; default: { return `${title}${styleText('cyan', S_BAR)} ${ diff --git a/packages/prompts/src/group-multi-select.ts b/packages/prompts/src/group-multi-select.ts index dd5ea304..b9f28037 100644 --- a/packages/prompts/src/group-multi-select.ts +++ b/packages/prompts/src/group-multi-select.ts @@ -1,4 +1,3 @@ -// import color from 'picocolors'; import { styleText } from 'node:util'; import { GroupMultiSelectPrompt } from '@clack/core'; @@ -67,7 +66,7 @@ export const groupMultiselect = (opts: GroupMultiSelectOptions) => }`; } if (state === 'cancelled') { - return `${styleText('strikethrough', styleText('dim', label))}`; + return `${styleText(['strikethrough', 'dim'], label)}`; } if (state === 'active-selected') { return `${spacingPrefix}${styleText('dim', prefix)}${styleText('green', S_CHECKBOX_SELECTED)} ${label}${ @@ -98,9 +97,9 @@ export const groupMultiselect = (opts: GroupMultiSelectOptions) => 'reset', styleText( 'dim', - `Press ${styleText('gray', styleText('bgWhite', styleText('inverse', ' space ')))} to select, ${styleText( + `Press ${styleText(['gray', 'bgWhite', 'inverse'], ' space ')} to select, ${styleText( 'gray', - styleText('bgWhite', styleText('inverse', ' enter ')) + styleText(['bgWhite', 'inverse'], ' enter ') )} to submit` ) )}`; diff --git a/packages/prompts/src/multi-select.ts b/packages/prompts/src/multi-select.ts index dc384050..d70c6887 100644 --- a/packages/prompts/src/multi-select.ts +++ b/packages/prompts/src/multi-select.ts @@ -77,7 +77,7 @@ export const multiselect = (opts: MultiSelectOptions) => { 'reset', styleText( 'dim', - `Press ${styleText('gray', styleText('bgWhite', styleText('inverse', ' space ')))} to select, ${styleText( + `Press ${styleText(['gray', 'bgWhite', 'inverse'], ' space ')} to select, ${styleText( 'gray', styleText('bgWhite', styleText('inverse', ' enter ')) )} to submit` diff --git a/packages/prompts/src/password.ts b/packages/prompts/src/password.ts index 3516f73c..a3c4a3ca 100644 --- a/packages/prompts/src/password.ts +++ b/packages/prompts/src/password.ts @@ -16,7 +16,6 @@ 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 title = `${styleText('gray', S_BAR)}\n${symbol(this.state)} ${opts.message}\n`; const userInput = this.userInputWithCursor; const masked = this.masked; @@ -38,7 +37,7 @@ export const password = (opts: PasswordOptions) => { } case 'cancel': { const maskedText = masked - ? ` ${styleText('strikethrough', styleText('dim', masked))}` + ? ` ${styleText(['strikethrough', 'dim'], masked)}` : ''; return `${title}${styleText('gray', S_BAR)}${maskedText}${ masked ? `\n${styleText('gray', S_BAR)}` : '' diff --git a/packages/prompts/src/select-key.ts b/packages/prompts/src/select-key.ts index 6d53ddd2..93fe899a 100644 --- a/packages/prompts/src/select-key.ts +++ b/packages/prompts/src/select-key.ts @@ -13,14 +13,14 @@ export const selectKey = (opts: SelectOptions) => { return `${styleText('dim', label)}`; } if (state === 'cancelled') { - return `${styleText('strikethrough', styleText('dim', label))}`; + return `${styleText(['strikethrough', 'dim'], label)}`; } if (state === 'active') { - return `${styleText('bgCyan', styleText('gray', ` ${option.value} `))} ${label} ${ + return `${styleText(['bgCyan', 'gray'], ` ${option.value} `)} ${label} ${ option.hint ? styleText('dim', `(${option.hint})`) : '' }`; } - return `${styleText('gray', styleText('bgWhite', styleText('inverse', ` ${option.value} `)))} ${label} ${ + return `${styleText(['gray', 'bgWhite', 'inverse'], ` ${option.value} `)} ${label} ${ option.hint ? styleText('dim', `(${option.hint})`) : '' }`; }; diff --git a/packages/prompts/src/select.ts b/packages/prompts/src/select.ts index 46e22b82..996f33a9 100644 --- a/packages/prompts/src/select.ts +++ b/packages/prompts/src/select.ts @@ -89,7 +89,7 @@ export const select = (opts: SelectOptions) => { option.hint ? ` ${styleText('dim', `(${option.hint})`)}` : '' }`; case 'cancelled': - return `${styleText('strikethrough', styleText('dim', label))}`; + return `${styleText(['strikethrough', 'dim'], label)}`; default: return `${styleText('dim', S_RADIO_INACTIVE)} ${styleText('dim', label)}`; } diff --git a/packages/prompts/src/text.ts b/packages/prompts/src/text.ts index 4a94ff3c..0a362e80 100644 --- a/packages/prompts/src/text.ts +++ b/packages/prompts/src/text.ts @@ -23,7 +23,7 @@ export const text = (opts: TextOptions) => { const title = `${styleText('gray', S_BAR)}\n${symbol(this.state)} ${opts.message}\n`; const placeholder = opts.placeholder ? styleText('inverse', opts.placeholder[0]) + styleText('dim', opts.placeholder.slice(1)) - : styleText('inverse', styleText('hidden', '_')); + : styleText(['inverse', 'hidden'], '_'); const userInput = !this.userInput ? placeholder : this.userInputWithCursor; const value = this.value ?? ''; @@ -40,7 +40,7 @@ export const text = (opts: TextOptions) => { return `${title}${styleText('gray', S_BAR)}${valueText}`; } case 'cancel': { - const valueText = value ? ` ${styleText('strikethrough', styleText('dim', value))}` : ''; + const valueText = value ? ` ${styleText(['strikethrough', 'dim'], value)}` : ''; return `${title}${styleText('gray', S_BAR)}${valueText}${value.trim() ? `\n${styleText('gray', S_BAR)}` : ''}`; } default: From 98c8c2aeeb09d9adb59d37c09e171c85cc7edca1 Mon Sep 17 00:00:00 2001 From: paul valladares <85648028+dreyfus92@users.noreply.github.com> Date: Thu, 16 Oct 2025 11:03:04 -0500 Subject: [PATCH 4/4] Update packages/prompts/src/group-multi-select.ts Co-authored-by: James Garbutt <43081j@users.noreply.github.com> --- packages/prompts/src/group-multi-select.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/prompts/src/group-multi-select.ts b/packages/prompts/src/group-multi-select.ts index b9f28037..80b3d1fb 100644 --- a/packages/prompts/src/group-multi-select.ts +++ b/packages/prompts/src/group-multi-select.ts @@ -1,6 +1,5 @@ import { styleText } from 'node:util'; import { GroupMultiSelectPrompt } from '@clack/core'; - import { type CommonOptions, S_BAR,