[READ-ONLY] Mirror of https://github.com/bombshell-dev/clack. Effortlessly build beautiful command-line apps bomb.sh/docs/clack/basics/getting-started/
cli command-line command-line-app node prompt prompts
5

Configure Feed

Select the types of activity you want to include in your feed.

refactor(core, prompts): replace picocolors with styleText (#403)

Co-authored-by: James Garbutt <43081j@users.noreply.github.com>

authored by

paul valladares
James Garbutt
and committed by
GitHub
(Feb 24, 2026, 9:45 AM UTC) e3333fbf 594c58a7

+319 -275
+6
.changeset/huge-items-throw.md
··· 1 + --- 2 + "@clack/prompts": minor 3 + "@clack/core": minor 4 + --- 5 + 6 + Replaces `picocolors` with Node.js built-in `styleText`.
-1
packages/core/package.json
··· 53 53 "test": "vitest run" 54 54 }, 55 55 "dependencies": { 56 - "picocolors": "^1.0.0", 57 56 "sisteransi": "^1.0.5" 58 57 }, 59 58 "devDependencies": {
+3 -3
packages/core/src/prompts/autocomplete.ts
··· 1 1 import type { Key } from 'node:readline'; 2 - import color from 'picocolors'; 2 + import { styleText } from 'node:util'; 3 3 import { findCursor } from '../utils/cursor.js'; 4 4 import Prompt, { type PromptOptions } from './prompt.js'; 5 5 ··· 73 73 74 74 get userInputWithCursor() { 75 75 if (!this.userInput) { 76 - return color.inverse(color.hidden('_')); 76 + return styleText(['inverse', 'hidden'], '_'); 77 77 } 78 78 if (this._cursor >= this.userInput.length) { 79 79 return `${this.userInput}█`; 80 80 } 81 81 const s1 = this.userInput.slice(0, this._cursor); 82 82 const [s2, ...s3] = this.userInput.slice(this._cursor); 83 - return `${s1}${color.inverse(s2)}${s3.join('')}`; 83 + return `${s1}${styleText('inverse', s2)}${s3.join('')}`; 84 84 } 85 85 86 86 get options(): T[] {
+3 -3
packages/core/src/prompts/password.ts
··· 1 - import color from 'picocolors'; 1 + import { styleText } from 'node:util'; 2 2 import Prompt, { type PromptOptions } from './prompt.js'; 3 3 4 4 export interface PasswordOptions extends PromptOptions<string, PasswordPrompt> { ··· 18 18 } 19 19 const userInput = this.userInput; 20 20 if (this.cursor >= userInput.length) { 21 - return `${this.masked}${color.inverse(color.hidden('_'))}`; 21 + return `${this.masked}${styleText(['inverse', 'hidden'], '_')}`; 22 22 } 23 23 const masked = this.masked; 24 24 const s1 = masked.slice(0, this.cursor); 25 25 const s2 = masked.slice(this.cursor); 26 - return `${s1}${color.inverse(s2[0])}${s2.slice(1)}`; 26 + return `${s1}${styleText('inverse', s2[0])}${s2.slice(1)}`; 27 27 } 28 28 clear() { 29 29 this._clearUserInput();
+2 -2
packages/core/src/prompts/text.ts
··· 1 - import color from 'picocolors'; 1 + import { styleText } from 'node:util'; 2 2 import Prompt, { type PromptOptions } from './prompt.js'; 3 3 4 4 export interface TextOptions extends PromptOptions<string, TextPrompt> { ··· 17 17 } 18 18 const s1 = userInput.slice(0, this.cursor); 19 19 const [s2, ...s3] = userInput.slice(this.cursor); 20 - return `${s1}${color.inverse(s2)}${s3.join('')}`; 20 + return `${s1}${styleText('inverse', s2)}${s3.join('')}`; 21 21 } 22 22 get cursor() { 23 23 return this._cursor;
+8 -4
packages/core/test/prompts/password.test.ts
··· 1 - import color from 'picocolors'; 1 + import { styleText } from 'node:util'; 2 2 import { cursor } from 'sisteransi'; 3 3 import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; 4 4 import { default as PasswordPrompt } from '../../src/prompts/password.js'; ··· 65 65 }); 66 66 instance.prompt(); 67 67 input.emit('keypress', 'x', { name: 'x' }); 68 - expect(instance.userInputWithCursor).to.equal(`•${color.inverse(color.hidden('_'))}`); 68 + expect(instance.userInputWithCursor).to.equal( 69 + `•${styleText(['inverse', 'hidden'], '_')}` 70 + ); 69 71 }); 70 72 71 73 test('renders cursor inside value', () => { ··· 80 82 input.emit('keypress', 'z', { name: 'z' }); 81 83 input.emit('keypress', 'left', { name: 'left' }); 82 84 input.emit('keypress', 'left', { name: 'left' }); 83 - expect(instance.userInputWithCursor).to.equal(`•${color.inverse('•')}•`); 85 + expect(instance.userInputWithCursor).to.equal(`•${styleText('inverse', '•')}•`); 84 86 }); 85 87 86 88 test('renders custom mask', () => { ··· 92 94 }); 93 95 instance.prompt(); 94 96 input.emit('keypress', 'x', { name: 'x' }); 95 - expect(instance.userInputWithCursor).to.equal(`X${color.inverse(color.hidden('_'))}`); 97 + expect(instance.userInputWithCursor).to.equal( 98 + `X${styleText(['inverse', 'hidden'], '_')}` 99 + ); 96 100 }); 97 101 }); 98 102 });
+2 -2
packages/core/test/prompts/text.test.ts
··· 1 - import color from 'picocolors'; 1 + import { styleText } from 'node:util'; 2 2 import { cursor } from 'sisteransi'; 3 3 import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; 4 4 import { default as TextPrompt } from '../../src/prompts/text.js'; ··· 93 93 input.emit('keypress', keys[i], { name: keys[i] }); 94 94 } 95 95 input.emit('keypress', 'left', { name: 'left' }); 96 - expect(instance.userInputWithCursor).to.equal(`fo${color.inverse('o')}`); 96 + expect(instance.userInputWithCursor).to.equal(`fo${styleText('inverse', 'o')}`); 97 97 }); 98 98 99 99 test('shows cursor at end if beyond value', () => {
-1
packages/prompts/package.json
··· 54 54 }, 55 55 "dependencies": { 56 56 "@clack/core": "workspace:*", 57 - "picocolors": "^1.0.0", 58 57 "sisteransi": "^1.0.5" 59 58 }, 60 59 "devDependencies": {
+52 -42
packages/prompts/src/autocomplete.ts
··· 1 + import { styleText } from 'node:util'; 1 2 import { AutocompletePrompt, settings } from '@clack/core'; 2 - import color from 'picocolors'; 3 3 import { 4 4 type CommonOptions, 5 5 S_BAR, ··· 98 98 const hasGuide = opts.withGuide ?? settings.withGuide; 99 99 // Title and message display 100 100 const headings = hasGuide 101 - ? [`${color.gray(S_BAR)}`, `${symbol(this.state)} ${opts.message}`] 101 + ? [`${styleText('gray', S_BAR)}`, `${symbol(this.state)} ${opts.message}`] 102 102 : [`${symbol(this.state)} ${opts.message}`]; 103 103 const userInput = this.userInput; 104 104 const options = this.options; ··· 107 107 const opt = (option: Option<Value>, state: 'inactive' | 'active' | 'disabled') => { 108 108 const label = getLabel(option); 109 109 const hint = 110 - option.hint && option.value === this.focusedValue ? color.dim(` (${option.hint})`) : ''; 110 + option.hint && option.value === this.focusedValue 111 + ? styleText('dim', ` (${option.hint})`) 112 + : ''; 111 113 switch (state) { 112 114 case 'active': 113 - return `${color.green(S_RADIO_ACTIVE)} ${label}${hint}`; 115 + return `${styleText('green', S_RADIO_ACTIVE)} ${label}${hint}`; 114 116 case 'inactive': 115 - return `${color.dim(S_RADIO_INACTIVE)} ${color.dim(label)}`; 117 + return `${styleText('dim', S_RADIO_INACTIVE)} ${styleText('dim', label)}`; 116 118 case 'disabled': 117 - return `${color.gray(S_RADIO_INACTIVE)} ${color.strikethrough(color.gray(label))}`; 119 + return `${styleText('gray', S_RADIO_INACTIVE)} ${styleText(['strikethrough', 'gray'], label)}`; 118 120 } 119 121 }; 120 122 ··· 124 126 // Show selected value 125 127 const selected = getSelectedOptions(this.selectedValues, options); 126 128 const label = 127 - selected.length > 0 ? ` ${color.dim(selected.map(getLabel).join(', '))}` : ''; 128 - const submitPrefix = hasGuide ? color.gray(S_BAR) : ''; 129 + selected.length > 0 ? ` ${styleText('dim', selected.map(getLabel).join(', '))}` : ''; 130 + const submitPrefix = hasGuide ? styleText('gray', S_BAR) : ''; 129 131 return `${headings.join('\n')}\n${submitPrefix}${label}`; 130 132 } 131 133 132 134 case 'cancel': { 133 - const userInputText = userInput ? ` ${color.strikethrough(color.dim(userInput))}` : ''; 134 - const cancelPrefix = hasGuide ? color.gray(S_BAR) : ''; 135 + const userInputText = userInput 136 + ? ` ${styleText(['strikethrough', 'dim'], userInput)}` 137 + : ''; 138 + const cancelPrefix = hasGuide ? styleText('gray', S_BAR) : ''; 135 139 return `${headings.join('\n')}\n${cancelPrefix}${userInputText}`; 136 140 } 137 141 138 142 default: { 139 - const barColor = this.state === 'error' ? color.yellow : color.cyan; 140 - const guidePrefix = hasGuide ? `${barColor(S_BAR)} ` : ''; 141 - const guidePrefixEnd = hasGuide ? barColor(S_BAR_END) : ''; 143 + const barStyle = this.state === 'error' ? 'yellow' : 'cyan'; 144 + const guidePrefix = hasGuide ? `${styleText(barStyle, S_BAR)} ` : ''; 145 + const guidePrefixEnd = hasGuide ? styleText(barStyle, S_BAR_END) : ''; 142 146 // Display cursor position - show plain text in navigation mode 143 147 let searchText = ''; 144 148 if (this.isNavigating || showPlaceholder) { 145 149 const searchTextValue = showPlaceholder ? placeholder : userInput; 146 - searchText = searchTextValue !== '' ? ` ${color.dim(searchTextValue)}` : ''; 150 + searchText = searchTextValue !== '' ? ` ${styleText('dim', searchTextValue)}` : ''; 147 151 } else { 148 152 searchText = ` ${this.userInputWithCursor}`; 149 153 } ··· 151 155 // Show match count if filtered 152 156 const matches = 153 157 this.filteredOptions.length !== options.length 154 - ? color.dim( 158 + ? styleText( 159 + 'dim', 155 160 ` (${this.filteredOptions.length} match${this.filteredOptions.length === 1 ? '' : 'es'})` 156 161 ) 157 162 : ''; ··· 159 164 // No matches message 160 165 const noResults = 161 166 this.filteredOptions.length === 0 && userInput 162 - ? [`${guidePrefix}${color.yellow('No matches found')}`] 167 + ? [`${guidePrefix}${styleText('yellow', 'No matches found')}`] 163 168 : []; 164 169 165 170 const validationError = 166 - this.state === 'error' ? [`${guidePrefix}${color.yellow(this.error)}`] : []; 171 + this.state === 'error' ? [`${guidePrefix}${styleText('yellow', this.error)}`] : []; 167 172 168 173 if (hasGuide) { 169 174 headings.push(`${guidePrefix.trimEnd()}`); 170 175 } 171 176 headings.push( 172 - `${guidePrefix}${color.dim('Search:')}${searchText}${matches}`, 177 + `${guidePrefix}${styleText('dim', 'Search:')}${searchText}${matches}`, 173 178 ...noResults, 174 179 ...validationError 175 180 ); 176 181 177 182 // Show instructions 178 183 const instructions = [ 179 - `${color.dim('↑/↓')} to select`, 180 - `${color.dim('Enter:')} confirm`, 181 - `${color.dim('Type:')} to search`, 184 + `${styleText('dim', '↑/↓')} to select`, 185 + `${styleText('dim', 'Enter:')} confirm`, 186 + `${styleText('dim', 'Type:')} to search`, 182 187 ]; 183 188 184 189 const footers = [`${guidePrefix}${instructions.join(' • ')}`, guidePrefixEnd]; ··· 243 248 const label = option.label ?? String(option.value ?? ''); 244 249 const hint = 245 250 option.hint && focusedValue !== undefined && option.value === focusedValue 246 - ? color.dim(` (${option.hint})`) 251 + ? styleText('dim', ` (${option.hint})`) 247 252 : ''; 248 - const checkbox = isSelected ? color.green(S_CHECKBOX_SELECTED) : color.dim(S_CHECKBOX_INACTIVE); 253 + const checkbox = isSelected 254 + ? styleText('green', S_CHECKBOX_SELECTED) 255 + : styleText('dim', S_CHECKBOX_INACTIVE); 249 256 250 257 if (option.disabled) { 251 - return `${color.gray(S_CHECKBOX_INACTIVE)} ${color.strikethrough(color.gray(label))}`; 258 + return `${styleText('gray', S_CHECKBOX_INACTIVE)} ${styleText(['strikethrough', 'gray'], label)}`; 252 259 } 253 260 if (active) { 254 261 return `${checkbox} ${label}${hint}`; 255 262 } 256 - return `${checkbox} ${color.dim(label)}`; 263 + return `${checkbox} ${styleText('dim', label)}`; 257 264 }; 258 265 259 266 // Create text prompt which we'll use as foundation ··· 277 284 output: opts.output, 278 285 render() { 279 286 // Title and symbol 280 - const title = `${color.gray(S_BAR)}\n${symbol(this.state)} ${opts.message}\n`; 287 + const title = `${styleText('gray', S_BAR)}\n${symbol(this.state)} ${opts.message}\n`; 281 288 282 289 // Selection counter 283 290 const userInput = this.userInput; ··· 287 294 // Search input display 288 295 const searchText = 289 296 this.isNavigating || showPlaceholder 290 - ? color.dim(showPlaceholder ? placeholder : userInput) // Just show plain text when in navigation mode 297 + ? styleText('dim', showPlaceholder ? placeholder : userInput) // Just show plain text when in navigation mode 291 298 : this.userInputWithCursor; 292 299 293 300 const options = this.options; 294 301 295 302 const matches = 296 303 this.filteredOptions.length !== options.length 297 - ? color.dim( 304 + ? styleText( 305 + 'dim', 298 306 ` (${this.filteredOptions.length} match${this.filteredOptions.length === 1 ? '' : 'es'})` 299 307 ) 300 308 : ''; ··· 302 310 // Render prompt state 303 311 switch (this.state) { 304 312 case 'submit': { 305 - return `${title}${color.gray(S_BAR)} ${color.dim(`${this.selectedValues.length} items selected`)}`; 313 + return `${title}${styleText('gray', S_BAR)} ${styleText('dim', `${this.selectedValues.length} items selected`)}`; 306 314 } 307 315 case 'cancel': { 308 - return `${title}${color.gray(S_BAR)} ${color.strikethrough(color.dim(userInput))}`; 316 + return `${title}${styleText('gray', S_BAR)} ${styleText(['strikethrough', 'dim'], userInput)}`; 309 317 } 310 318 default: { 311 - const barColor = this.state === 'error' ? color.yellow : color.cyan; 319 + const barStyle = this.state === 'error' ? 'yellow' : 'cyan'; 312 320 // Instructions 313 321 const instructions = [ 314 - `${color.dim('↑/↓')} to navigate`, 315 - `${color.dim(this.isNavigating ? 'Space/Tab:' : 'Tab:')} select`, 316 - `${color.dim('Enter:')} confirm`, 317 - `${color.dim('Type:')} to search`, 322 + `${styleText('dim', '↑/↓')} to navigate`, 323 + `${styleText('dim', this.isNavigating ? 'Space/Tab:' : 'Tab:')} select`, 324 + `${styleText('dim', 'Enter:')} confirm`, 325 + `${styleText('dim', 'Type:')} to search`, 318 326 ]; 319 327 320 328 // No results message 321 329 const noResults = 322 330 this.filteredOptions.length === 0 && userInput 323 - ? [`${barColor(S_BAR)} ${color.yellow('No matches found')}`] 331 + ? [`${styleText(barStyle, S_BAR)} ${styleText('yellow', 'No matches found')}`] 324 332 : []; 325 333 326 334 const errorMessage = 327 - this.state === 'error' ? [`${barColor(S_BAR)} ${color.yellow(this.error)}`] : []; 335 + this.state === 'error' 336 + ? [`${styleText(barStyle, S_BAR)} ${styleText('yellow', this.error)}`] 337 + : []; 328 338 329 339 // Calculate header and footer line counts for rowPadding 330 340 const headerLines = [ 331 - ...`${title}${barColor(S_BAR)}`.split('\n'), 332 - `${barColor(S_BAR)} ${color.dim('Search:')} ${searchText}${matches}`, 341 + ...`${title}${styleText(barStyle, S_BAR)}`.split('\n'), 342 + `${styleText(barStyle, S_BAR)} ${styleText('dim', 'Search:')} ${searchText}${matches}`, 333 343 ...noResults, 334 344 ...errorMessage, 335 345 ]; 336 346 const footerLines = [ 337 - `${barColor(S_BAR)} ${instructions.join(' • ')}`, 338 - `${barColor(S_BAR_END)}`, 347 + `${styleText(barStyle, S_BAR)} ${instructions.join(' • ')}`, 348 + styleText(barStyle, S_BAR_END), 339 349 ]; 340 350 341 351 // Get limited options for display ··· 352 362 // Build the prompt display 353 363 return [ 354 364 ...headerLines, 355 - ...displayOptions.map((option) => `${barColor(S_BAR)} ${option}`), 365 + ...displayOptions.map((option) => `${styleText(barStyle, S_BAR)} ${option}`), 356 366 ...footerLines, 357 367 ].join('\n'); 358 368 }
+9 -9
packages/prompts/src/common.ts
··· 1 1 import type { Readable, Writable } from 'node:stream'; 2 + import { styleText } from 'node:util'; 2 3 import type { State } from '@clack/core'; 3 4 import isUnicodeSupported from 'is-unicode-supported'; 4 - import color from 'picocolors'; 5 5 6 6 export const unicode = isUnicodeSupported(); 7 7 export const isCI = (): boolean => process.env.CI === 'true'; ··· 43 43 switch (state) { 44 44 case 'initial': 45 45 case 'active': 46 - return color.cyan(S_STEP_ACTIVE); 46 + return styleText('cyan', S_STEP_ACTIVE); 47 47 case 'cancel': 48 - return color.red(S_STEP_CANCEL); 48 + return styleText('red', S_STEP_CANCEL); 49 49 case 'error': 50 - return color.yellow(S_STEP_ERROR); 50 + return styleText('yellow', S_STEP_ERROR); 51 51 case 'submit': 52 - return color.green(S_STEP_SUBMIT); 52 + return styleText('green', S_STEP_SUBMIT); 53 53 } 54 54 }; 55 55 ··· 57 57 switch (state) { 58 58 case 'initial': 59 59 case 'active': 60 - return color.cyan(S_BAR); 60 + return styleText('cyan', S_BAR); 61 61 case 'cancel': 62 - return color.red(S_BAR); 62 + return styleText('red', S_BAR); 63 63 case 'error': 64 - return color.yellow(S_BAR); 64 + return styleText('yellow', S_BAR); 65 65 case 'submit': 66 - return color.green(S_BAR); 66 + return styleText('green', S_BAR); 67 67 } 68 68 }; 69 69
+15 -15
packages/prompts/src/confirm.ts
··· 1 + import { styleText } from 'node:util'; 1 2 import { ConfirmPrompt, settings } from '@clack/core'; 2 - import color from 'picocolors'; 3 3 import { 4 4 type CommonOptions, 5 5 S_BAR, ··· 28 28 initialValue: opts.initialValue ?? true, 29 29 render() { 30 30 const hasGuide = opts.withGuide ?? settings.withGuide; 31 - const title = `${hasGuide ? `${color.gray(S_BAR)}\n` : ''}${symbol(this.state)} ${opts.message}\n`; 31 + const title = `${hasGuide ? `${styleText('gray', S_BAR)}\n` : ''}${symbol(this.state)} ${opts.message}\n`; 32 32 const value = this.value ? active : inactive; 33 33 34 34 switch (this.state) { 35 35 case 'submit': { 36 - const submitPrefix = hasGuide ? `${color.gray(S_BAR)} ` : ''; 37 - return `${title}${submitPrefix}${color.dim(value)}`; 36 + const submitPrefix = hasGuide ? `${styleText('gray', S_BAR)} ` : ''; 37 + return `${title}${submitPrefix}${styleText('dim', value)}`; 38 38 } 39 39 case 'cancel': { 40 - const cancelPrefix = hasGuide ? `${color.gray(S_BAR)} ` : ''; 41 - return `${title}${cancelPrefix}${color.strikethrough( 42 - color.dim(value) 43 - )}${hasGuide ? `\n${color.gray(S_BAR)}` : ''}`; 40 + const cancelPrefix = hasGuide ? `${styleText('gray', S_BAR)} ` : ''; 41 + return `${title}${cancelPrefix}${styleText(['strikethrough', 'dim'], value)}${ 42 + hasGuide ? `\n${styleText('gray', S_BAR)}` : '' 43 + }`; 44 44 } 45 45 default: { 46 - const defaultPrefix = hasGuide ? `${color.cyan(S_BAR)} ` : ''; 47 - const defaultPrefixEnd = hasGuide ? color.cyan(S_BAR_END) : ''; 46 + const defaultPrefix = hasGuide ? `${styleText('cyan', S_BAR)} ` : ''; 47 + const defaultPrefixEnd = hasGuide ? styleText('cyan', S_BAR_END) : ''; 48 48 return `${title}${defaultPrefix}${ 49 49 this.value 50 - ? `${color.green(S_RADIO_ACTIVE)} ${active}` 51 - : `${color.dim(S_RADIO_INACTIVE)} ${color.dim(active)}` 52 - }${opts.vertical ? (hasGuide ? `\n${color.cyan(S_BAR)} ` : '\n') : ` ${color.dim('/')} `}${ 50 + ? `${styleText('green', S_RADIO_ACTIVE)} ${active}` 51 + : `${styleText('dim', S_RADIO_INACTIVE)} ${styleText('dim', active)}` 52 + }${opts.vertical ? (hasGuide ? `\n${styleText('cyan', S_BAR)} ` : '\n') : ` ${styleText('dim', '/')} `}${ 53 53 !this.value 54 - ? `${color.green(S_RADIO_ACTIVE)} ${inactive}` 55 - : `${color.dim(S_RADIO_INACTIVE)} ${color.dim(inactive)}` 54 + ? `${styleText('green', S_RADIO_ACTIVE)} ${inactive}` 55 + : `${styleText('dim', S_RADIO_INACTIVE)} ${styleText('dim', inactive)}` 56 56 }\n${defaultPrefixEnd}\n`; 57 57 } 58 58 }
+35 -30
packages/prompts/src/group-multi-select.ts
··· 1 + import { styleText } from 'node:util'; 1 2 import { GroupMultiSelectPrompt } from '@clack/core'; 2 - import color from 'picocolors'; 3 3 import { 4 4 type CommonOptions, 5 5 S_BAR, ··· 42 42 const prefix = isItem ? (selectableGroups ? `${isLast ? S_BAR_END : S_BAR} ` : ' ') : ''; 43 43 let spacingPrefix = ''; 44 44 if (groupSpacing > 0 && !isItem) { 45 - const spacingPrefixText = `\n${color.cyan(S_BAR)}`; 45 + const spacingPrefixText = `\n${styleText('cyan', S_BAR)}`; 46 46 spacingPrefix = `${spacingPrefixText.repeat(groupSpacing - 1)}${spacingPrefixText} `; 47 47 } 48 48 49 49 if (state === 'active') { 50 - return `${spacingPrefix}${color.dim(prefix)}${color.cyan(S_CHECKBOX_ACTIVE)} ${label}${ 51 - option.hint ? ` ${color.dim(`(${option.hint})`)}` : '' 50 + return `${spacingPrefix}${styleText('dim', prefix)}${styleText('cyan', S_CHECKBOX_ACTIVE)} ${label}${ 51 + option.hint ? ` ${styleText('dim', `(${option.hint})`)}` : '' 52 52 }`; 53 53 } 54 54 if (state === 'group-active') { 55 - return `${spacingPrefix}${prefix}${color.cyan(S_CHECKBOX_ACTIVE)} ${color.dim(label)}`; 55 + return `${spacingPrefix}${prefix}${styleText('cyan', S_CHECKBOX_ACTIVE)} ${styleText('dim', label)}`; 56 56 } 57 57 if (state === 'group-active-selected') { 58 - return `${spacingPrefix}${prefix}${color.green(S_CHECKBOX_SELECTED)} ${color.dim(label)}`; 58 + return `${spacingPrefix}${prefix}${styleText('green', S_CHECKBOX_SELECTED)} ${styleText('dim', label)}`; 59 59 } 60 60 if (state === 'selected') { 61 - const selectedCheckbox = isItem || selectableGroups ? color.green(S_CHECKBOX_SELECTED) : ''; 62 - return `${spacingPrefix}${color.dim(prefix)}${selectedCheckbox} ${color.dim(label)}${ 63 - option.hint ? ` ${color.dim(`(${option.hint})`)}` : '' 61 + const selectedCheckbox = 62 + isItem || selectableGroups ? styleText('green', S_CHECKBOX_SELECTED) : ''; 63 + return `${spacingPrefix}${styleText('dim', prefix)}${selectedCheckbox} ${styleText('dim', label)}${ 64 + option.hint ? ` ${styleText('dim', `(${option.hint})`)}` : '' 64 65 }`; 65 66 } 66 67 if (state === 'cancelled') { 67 - return `${color.strikethrough(color.dim(label))}`; 68 + return `${styleText(['strikethrough', 'dim'], label)}`; 68 69 } 69 70 if (state === 'active-selected') { 70 - return `${spacingPrefix}${color.dim(prefix)}${color.green(S_CHECKBOX_SELECTED)} ${label}${ 71 - option.hint ? ` ${color.dim(`(${option.hint})`)}` : '' 71 + return `${spacingPrefix}${styleText('dim', prefix)}${styleText('green', S_CHECKBOX_SELECTED)} ${label}${ 72 + option.hint ? ` ${styleText('dim', `(${option.hint})`)}` : '' 72 73 }`; 73 74 } 74 75 if (state === 'submitted') { 75 - return `${color.dim(label)}`; 76 + return `${styleText('dim', label)}`; 76 77 } 77 - const unselectedCheckbox = isItem || selectableGroups ? color.dim(S_CHECKBOX_INACTIVE) : ''; 78 - return `${spacingPrefix}${color.dim(prefix)}${unselectedCheckbox} ${color.dim(label)}`; 78 + const unselectedCheckbox = 79 + isItem || selectableGroups ? styleText('dim', S_CHECKBOX_INACTIVE) : ''; 80 + return `${spacingPrefix}${styleText('dim', prefix)}${unselectedCheckbox} ${styleText('dim', label)}`; 79 81 }; 80 82 const required = opts.required ?? true; 81 83 ··· 90 92 selectableGroups, 91 93 validate(selected: Value[] | undefined) { 92 94 if (required && (selected === undefined || selected.length === 0)) 93 - return `Please select at least one option.\n${color.reset( 94 - color.dim( 95 - `Press ${color.gray(color.bgWhite(color.inverse(' space ')))} to select, ${color.gray( 96 - color.bgWhite(color.inverse(' enter ')) 95 + return `Please select at least one option.\n${styleText( 96 + 'reset', 97 + styleText( 98 + 'dim', 99 + `Press ${styleText(['gray', 'bgWhite', 'inverse'], ' space ')} to select, ${styleText( 100 + 'gray', 101 + styleText(['bgWhite', 'inverse'], ' enter ') 97 102 )} to submit` 98 103 ) 99 104 )}`; 100 105 }, 101 106 render() { 102 - const title = `${color.gray(S_BAR)}\n${symbol(this.state)} ${opts.message}\n`; 107 + const title = `${styleText('gray', S_BAR)}\n${symbol(this.state)} ${opts.message}\n`; 103 108 const value = this.value ?? []; 104 109 105 110 switch (this.state) { ··· 108 113 .filter(({ value: optionValue }) => value.includes(optionValue)) 109 114 .map((option) => opt(option, 'submitted')); 110 115 const optionsText = 111 - selectedOptions.length === 0 ? '' : ` ${selectedOptions.join(color.dim(', '))}`; 112 - return `${title}${color.gray(S_BAR)}${optionsText}`; 116 + selectedOptions.length === 0 ? '' : ` ${selectedOptions.join(styleText('dim', ', '))}`; 117 + return `${title}${styleText('gray', S_BAR)}${optionsText}`; 113 118 } 114 119 case 'cancel': { 115 120 const label = this.options 116 121 .filter(({ value: optionValue }) => value.includes(optionValue)) 117 122 .map((option) => opt(option, 'cancelled')) 118 - .join(color.dim(', ')); 119 - return `${title}${color.gray(S_BAR)} ${ 120 - label.trim() ? `${label}\n${color.gray(S_BAR)}` : '' 123 + .join(styleText('dim', ', ')); 124 + return `${title}${styleText('gray', S_BAR)} ${ 125 + label.trim() ? `${label}\n${styleText('gray', S_BAR)}` : '' 121 126 }`; 122 127 } 123 128 case 'error': { 124 129 const footer = this.error 125 130 .split('\n') 126 131 .map((ln, i) => 127 - i === 0 ? `${color.yellow(S_BAR_END)} ${color.yellow(ln)}` : ` ${ln}` 132 + i === 0 ? `${styleText('yellow', S_BAR_END)} ${styleText('yellow', ln)}` : ` ${ln}` 128 133 ) 129 134 .join('\n'); 130 - return `${title}${color.yellow(S_BAR)} ${this.options 135 + return `${title}${styleText('yellow', S_BAR)} ${this.options 131 136 .map((option, i, options) => { 132 137 const selected = 133 138 value.includes(option.value) || ··· 148 153 } 149 154 return opt(option, active ? 'active' : 'inactive', options); 150 155 }) 151 - .join(`\n${color.yellow(S_BAR)} `)}\n${footer}\n`; 156 + .join(`\n${styleText('yellow', S_BAR)} `)}\n${footer}\n`; 152 157 } 153 158 default: { 154 159 const optionsText = this.options ··· 178 183 const prefix = i !== 0 && !optionText.startsWith('\n') ? ' ' : ''; 179 184 return `${prefix}${optionText}`; 180 185 }) 181 - .join(`\n${color.cyan(S_BAR)}`); 186 + .join(`\n${styleText('cyan', S_BAR)}`); 182 187 const optionsPrefix = optionsText.startsWith('\n') ? '' : ' '; 183 - return `${title}${color.cyan(S_BAR)}${optionsPrefix}${optionsText}\n${color.cyan(S_BAR_END)}\n`; 188 + return `${title}${styleText('cyan', S_BAR)}${optionsPrefix}${optionsText}\n${styleText('cyan', S_BAR_END)}\n`; 184 189 } 185 190 } 186 191 },
+2 -2
packages/prompts/src/limit-options.ts
··· 1 + import { styleText } from 'node:util'; 1 2 import { getColumns, getRows } from '@clack/core'; 2 3 import { wrapAnsi } from 'fast-wrap-ansi'; 3 - import color from 'picocolors'; 4 4 import type { CommonOptions } from './common.js'; 5 5 6 6 export interface LimitOptionsParams<TOption> extends CommonOptions { ··· 44 44 const columns = getColumns(output); 45 45 const maxWidth = columns - columnPadding; 46 46 const rows = getRows(output); 47 - const overflowFormat = color.dim('...'); 47 + const overflowFormat = styleText('dim', '...'); 48 48 49 49 const outputMaxItems = Math.max(rows - rowPadding, 0); 50 50 // We clamp to minimum 5 because anything less doesn't make sense UX wise
+8 -8
packages/prompts/src/log.ts
··· 1 + import { styleText } from 'node:util'; 1 2 import { settings } from '@clack/core'; 2 - import color from 'picocolors'; 3 3 import { 4 4 type CommonOptions, 5 5 S_BAR, ··· 20 20 message: ( 21 21 message: string | string[] = [], 22 22 { 23 - symbol = color.gray(S_BAR), 24 - secondarySymbol = color.gray(S_BAR), 23 + symbol = styleText('gray', S_BAR), 24 + secondarySymbol = styleText('gray', S_BAR), 25 25 output = process.stdout, 26 26 spacing = 1, 27 27 withGuide, ··· 56 56 output.write(`${parts.join('\n')}\n`); 57 57 }, 58 58 info: (message: string, opts?: LogMessageOptions) => { 59 - log.message(message, { ...opts, symbol: color.blue(S_INFO) }); 59 + log.message(message, { ...opts, symbol: styleText('blue', S_INFO) }); 60 60 }, 61 61 success: (message: string, opts?: LogMessageOptions) => { 62 - log.message(message, { ...opts, symbol: color.green(S_SUCCESS) }); 62 + log.message(message, { ...opts, symbol: styleText('green', S_SUCCESS) }); 63 63 }, 64 64 step: (message: string, opts?: LogMessageOptions) => { 65 - log.message(message, { ...opts, symbol: color.green(S_STEP_SUBMIT) }); 65 + log.message(message, { ...opts, symbol: styleText('green', S_STEP_SUBMIT) }); 66 66 }, 67 67 warn: (message: string, opts?: LogMessageOptions) => { 68 - log.message(message, { ...opts, symbol: color.yellow(S_WARN) }); 68 + log.message(message, { ...opts, symbol: styleText('yellow', S_WARN) }); 69 69 }, 70 70 /** alias for `log.warn()`. */ 71 71 warning: (message: string, opts?: LogMessageOptions) => { 72 72 log.warn(message, opts); 73 73 }, 74 74 error: (message: string, opts?: LogMessageOptions) => { 75 - log.message(message, { ...opts, symbol: color.red(S_ERROR) }); 75 + log.message(message, { ...opts, symbol: styleText('red', S_ERROR) }); 76 76 }, 77 77 };
+4 -4
packages/prompts/src/messages.ts
··· 1 1 import type { Writable } from 'node:stream'; 2 - import color from 'picocolors'; 2 + import { styleText } from 'node:util'; 3 3 import { type CommonOptions, S_BAR, S_BAR_END, S_BAR_START } from './common.js'; 4 4 5 5 export const cancel = (message = '', opts?: CommonOptions) => { 6 6 const output: Writable = opts?.output ?? process.stdout; 7 - output.write(`${color.gray(S_BAR_END)} ${color.red(message)}\n\n`); 7 + output.write(`${styleText('gray', S_BAR_END)} ${styleText('red', message)}\n\n`); 8 8 }; 9 9 10 10 export const intro = (title = '', opts?: CommonOptions) => { 11 11 const output: Writable = opts?.output ?? process.stdout; 12 - output.write(`${color.gray(S_BAR_START)} ${title}\n`); 12 + output.write(`${styleText('gray', S_BAR_START)} ${title}\n`); 13 13 }; 14 14 15 15 export const outro = (message = '', opts?: CommonOptions) => { 16 16 const output: Writable = opts?.output ?? process.stdout; 17 - output.write(`${color.gray(S_BAR)}\n${color.gray(S_BAR_END)} ${message}\n\n`); 17 + output.write(`${styleText('gray', S_BAR)}\n${styleText('gray', S_BAR_END)} ${message}\n\n`); 18 18 };
+34 -27
packages/prompts/src/multi-select.ts
··· 1 + import { styleText } from 'node:util'; 1 2 import { MultiSelectPrompt, wrapTextWithPrefix } from '@clack/core'; 2 - import color from 'picocolors'; 3 3 import { 4 4 type CommonOptions, 5 5 S_BAR, ··· 42 42 ) => { 43 43 const label = option.label ?? String(option.value); 44 44 if (state === 'disabled') { 45 - return `${color.gray(S_CHECKBOX_INACTIVE)} ${computeLabel(label, (str) => color.strikethrough(color.gray(str)))}${ 46 - option.hint ? ` ${color.dim(`(${option.hint ?? 'disabled'})`)}` : '' 45 + return `${styleText('gray', S_CHECKBOX_INACTIVE)} ${computeLabel(label, (str) => styleText(['strikethrough', 'gray'], str))}${ 46 + option.hint ? ` ${styleText('dim', `(${option.hint ?? 'disabled'})`)}` : '' 47 47 }`; 48 48 } 49 49 if (state === 'active') { 50 - return `${color.cyan(S_CHECKBOX_ACTIVE)} ${label}${ 51 - option.hint ? ` ${color.dim(`(${option.hint})`)}` : '' 50 + return `${styleText('cyan', S_CHECKBOX_ACTIVE)} ${label}${ 51 + option.hint ? ` ${styleText('dim', `(${option.hint})`)}` : '' 52 52 }`; 53 53 } 54 54 if (state === 'selected') { 55 - return `${color.green(S_CHECKBOX_SELECTED)} ${computeLabel(label, color.dim)}${ 56 - option.hint ? ` ${color.dim(`(${option.hint})`)}` : '' 55 + return `${styleText('green', S_CHECKBOX_SELECTED)} ${computeLabel(label, (text) => styleText('dim', text))}${ 56 + option.hint ? ` ${styleText('dim', `(${option.hint})`)}` : '' 57 57 }`; 58 58 } 59 59 if (state === 'cancelled') { 60 - return `${computeLabel(label, (text) => color.strikethrough(color.dim(text)))}`; 60 + return `${computeLabel(label, (text) => styleText(['strikethrough', 'dim'], text))}`; 61 61 } 62 62 if (state === 'active-selected') { 63 - return `${color.green(S_CHECKBOX_SELECTED)} ${label}${ 64 - option.hint ? ` ${color.dim(`(${option.hint})`)}` : '' 63 + return `${styleText('green', S_CHECKBOX_SELECTED)} ${label}${ 64 + option.hint ? ` ${styleText('dim', `(${option.hint})`)}` : '' 65 65 }`; 66 66 } 67 67 if (state === 'submitted') { 68 - return `${computeLabel(label, color.dim)}`; 68 + return `${computeLabel(label, (text) => styleText('dim', text))}`; 69 69 } 70 - return `${color.dim(S_CHECKBOX_INACTIVE)} ${computeLabel(label, color.dim)}`; 70 + return `${styleText('dim', S_CHECKBOX_INACTIVE)} ${computeLabel(label, (text) => styleText('dim', text))}`; 71 71 }; 72 72 const required = opts.required ?? true; 73 73 ··· 81 81 cursorAt: opts.cursorAt, 82 82 validate(selected: Value[] | undefined) { 83 83 if (required && (selected === undefined || selected.length === 0)) 84 - return `Please select at least one option.\n${color.reset( 85 - color.dim( 86 - `Press ${color.gray(color.bgWhite(color.inverse(' space ')))} to select, ${color.gray( 87 - color.bgWhite(color.inverse(' enter ')) 84 + return `Please select at least one option.\n${styleText( 85 + 'reset', 86 + styleText( 87 + 'dim', 88 + `Press ${styleText(['gray', 'bgWhite', 'inverse'], ' space ')} to select, ${styleText( 89 + 'gray', 90 + styleText('bgWhite', styleText('inverse', ' enter ')) 88 91 )} to submit` 89 92 ) 90 93 )}`; ··· 96 99 `${symbolBar(this.state)} `, 97 100 `${symbol(this.state)} ` 98 101 ); 99 - const title = `${color.gray(S_BAR)}\n${wrappedMessage}\n`; 102 + const title = `${styleText('gray', S_BAR)}\n${wrappedMessage}\n`; 100 103 const value = this.value ?? []; 101 104 102 105 const styleOption = (option: Option<Value>, active: boolean) => { ··· 119 122 this.options 120 123 .filter(({ value: optionValue }) => value.includes(optionValue)) 121 124 .map((option) => opt(option, 'submitted')) 122 - .join(color.dim(', ')) || color.dim('none'); 125 + .join(styleText('dim', ', ')) || styleText('dim', 'none'); 123 126 const wrappedSubmitText = wrapTextWithPrefix( 124 127 opts.output, 125 128 submitText, 126 - `${color.gray(S_BAR)} ` 129 + `${styleText('gray', S_BAR)} ` 127 130 ); 128 131 return `${title}${wrappedSubmitText}`; 129 132 } ··· 131 134 const label = this.options 132 135 .filter(({ value: optionValue }) => value.includes(optionValue)) 133 136 .map((option) => opt(option, 'cancelled')) 134 - .join(color.dim(', ')); 137 + .join(styleText('dim', ', ')); 135 138 if (label.trim() === '') { 136 - return `${title}${color.gray(S_BAR)}`; 139 + return `${title}${styleText('gray', S_BAR)}`; 137 140 } 138 - const wrappedLabel = wrapTextWithPrefix(opts.output, label, `${color.gray(S_BAR)} `); 139 - return `${title}${wrappedLabel}\n${color.gray(S_BAR)}`; 141 + const wrappedLabel = wrapTextWithPrefix( 142 + opts.output, 143 + label, 144 + `${styleText('gray', S_BAR)} ` 145 + ); 146 + return `${title}${wrappedLabel}\n${styleText('gray', S_BAR)}`; 140 147 } 141 148 case 'error': { 142 - const prefix = `${color.yellow(S_BAR)} `; 149 + const prefix = `${styleText('yellow', S_BAR)} `; 143 150 const footer = this.error 144 151 .split('\n') 145 152 .map((ln, i) => 146 - i === 0 ? `${color.yellow(S_BAR_END)} ${color.yellow(ln)}` : ` ${ln}` 153 + i === 0 ? `${styleText('yellow', S_BAR_END)} ${styleText('yellow', ln)}` : ` ${ln}` 147 154 ) 148 155 .join('\n'); 149 156 // Calculate rowPadding: title lines + footer lines (error message + trailing newline) ··· 160 167 }).join(`\n${prefix}`)}\n${footer}\n`; 161 168 } 162 169 default: { 163 - const prefix = `${color.cyan(S_BAR)} `; 170 + const prefix = `${styleText('cyan', S_BAR)} `; 164 171 // Calculate rowPadding: title lines + footer lines (S_BAR_END + trailing newline) 165 172 const titleLineCount = title.split('\n').length; 166 173 const footerLineCount = 2; // S_BAR_END + trailing newline ··· 172 179 columnPadding: prefix.length, 173 180 rowPadding: titleLineCount + footerLineCount, 174 181 style: styleOption, 175 - }).join(`\n${prefix}`)}\n${color.cyan(S_BAR_END)}\n`; 182 + }).join(`\n${prefix}`)}\n${styleText('cyan', S_BAR_END)}\n`; 176 183 } 177 184 } 178 185 },
+8 -6
packages/prompts/src/note.ts
··· 1 1 import process from 'node:process'; 2 2 import type { Writable } from 'node:stream'; 3 + import { styleText } from 'node:util'; 3 4 import { getColumns, settings } from '@clack/core'; 4 5 import stringWidth from 'fast-string-width'; 5 6 import { type Options as WrapAnsiOptions, wrapAnsi } from 'fast-wrap-ansi'; 6 - import color from 'picocolors'; 7 7 import { 8 8 type CommonOptions, 9 9 S_BAR, ··· 20 20 format?: FormatFn; 21 21 } 22 22 23 - const defaultNoteFormatter = (line: string): string => color.dim(line); 23 + const defaultNoteFormatter = (line: string): string => styleText('dim', line); 24 24 25 25 const wrapWithFormat = (message: string, width: number, format: FormatFn): string => { 26 26 const opts: WrapAnsiOptions = { ··· 51 51 ) + 2; 52 52 const msg = lines 53 53 .map( 54 - (ln) => `${color.gray(S_BAR)} ${ln}${' '.repeat(len - stringWidth(ln))}${color.gray(S_BAR)}` 54 + (ln) => 55 + `${styleText('gray', S_BAR)} ${ln}${' '.repeat(len - stringWidth(ln))}${styleText('gray', S_BAR)}` 55 56 ) 56 57 .join('\n'); 57 - const leadingBorder = hasGuide ? `${color.gray(S_BAR)}\n` : ''; 58 + const leadingBorder = hasGuide ? `${styleText('gray', S_BAR)}\n` : ''; 58 59 const bottomLeft = hasGuide ? S_CONNECT_LEFT : S_CORNER_BOTTOM_LEFT; 59 60 output.write( 60 - `${leadingBorder}${color.green(S_STEP_SUBMIT)} ${color.reset(title)} ${color.gray( 61 + `${leadingBorder}${styleText('green', S_STEP_SUBMIT)} ${styleText('reset', title)} ${styleText( 62 + 'gray', 61 63 S_BAR_H.repeat(Math.max(len - titleLen - 1, 1)) + S_CORNER_TOP_RIGHT 62 - )}\n${msg}\n${color.gray(bottomLeft + S_BAR_H.repeat(len + 2) + S_CORNER_BOTTOM_RIGHT)}\n` 64 + )}\n${msg}\n${styleText('gray', bottomLeft + S_BAR_H.repeat(len + 2) + S_CORNER_BOTTOM_RIGHT)}\n` 63 65 ); 64 66 };
+12 -12
packages/prompts/src/password.ts
··· 1 + import { styleText } from 'node:util'; 1 2 import { PasswordPrompt, settings } from '@clack/core'; 2 - import color from 'picocolors'; 3 3 import { type CommonOptions, S_BAR, S_BAR_END, S_PASSWORD_MASK, symbol } from './common.js'; 4 4 5 5 export interface PasswordOptions extends CommonOptions { ··· 17 17 output: opts.output, 18 18 render() { 19 19 const hasGuide = opts.withGuide ?? settings.withGuide; 20 - const title = `${hasGuide ? `${color.gray(S_BAR)}\n` : ''}${symbol(this.state)} ${opts.message}\n`; 20 + const title = `${hasGuide ? `${styleText('gray', S_BAR)}\n` : ''}${symbol(this.state)} ${opts.message}\n`; 21 21 const userInput = this.userInputWithCursor; 22 22 const masked = this.masked; 23 23 24 24 switch (this.state) { 25 25 case 'error': { 26 - const errorPrefix = hasGuide ? `${color.yellow(S_BAR)} ` : ''; 27 - const errorPrefixEnd = hasGuide ? `${color.yellow(S_BAR_END)} ` : ''; 26 + const errorPrefix = hasGuide ? `${styleText('yellow', S_BAR)} ` : ''; 27 + const errorPrefixEnd = hasGuide ? `${styleText('yellow', S_BAR_END)} ` : ''; 28 28 const maskedText = masked ?? ''; 29 29 if (opts.clearOnError) { 30 30 this.clear(); 31 31 } 32 - return `${title.trim()}\n${errorPrefix}${maskedText}\n${errorPrefixEnd}${color.yellow(this.error)}\n`; 32 + return `${title.trim()}\n${errorPrefix}${maskedText}\n${errorPrefixEnd}${styleText('yellow', this.error)}\n`; 33 33 } 34 34 case 'submit': { 35 - const submitPrefix = hasGuide ? `${color.gray(S_BAR)} ` : ''; 36 - const maskedText = masked ? color.dim(masked) : ''; 35 + const submitPrefix = hasGuide ? `${styleText('gray', S_BAR)} ` : ''; 36 + const maskedText = masked ? styleText('dim', masked) : ''; 37 37 return `${title}${submitPrefix}${maskedText}`; 38 38 } 39 39 case 'cancel': { 40 - const cancelPrefix = hasGuide ? `${color.gray(S_BAR)} ` : ''; 41 - const maskedText = masked ? color.strikethrough(color.dim(masked)) : ''; 40 + const cancelPrefix = hasGuide ? `${styleText('gray', S_BAR)} ` : ''; 41 + const maskedText = masked ? styleText(['strikethrough', 'dim'], masked) : ''; 42 42 return `${title}${cancelPrefix}${maskedText}${ 43 - masked && hasGuide ? `\n${color.gray(S_BAR)}` : '' 43 + masked && hasGuide ? `\n${styleText('gray', S_BAR)}` : '' 44 44 }`; 45 45 } 46 46 default: { 47 - const defaultPrefix = hasGuide ? `${color.cyan(S_BAR)} ` : ''; 48 - const defaultPrefixEnd = hasGuide ? color.cyan(S_BAR_END) : ''; 47 + const defaultPrefix = hasGuide ? `${styleText('cyan', S_BAR)} ` : ''; 48 + const defaultPrefixEnd = hasGuide ? styleText('cyan', S_BAR_END) : ''; 49 49 return `${title}${defaultPrefix}${userInput}\n${defaultPrefixEnd}\n`; 50 50 } 51 51 }
+6 -6
packages/prompts/src/progress-bar.ts
··· 1 + import { styleText } from 'node:util'; 1 2 import type { State } from '@clack/core'; 2 - import color from 'picocolors'; 3 3 import { unicodeOr } from './common.js'; 4 4 import { type SpinnerOptions, type SpinnerResult, spinner } from './spinner.js'; 5 5 ··· 36 36 switch (state) { 37 37 case 'initial': 38 38 case 'active': 39 - return color.magenta; 39 + return (text: string) => styleText('magenta', text); 40 40 case 'error': 41 41 case 'cancel': 42 - return color.red; 42 + return (text: string) => styleText('red', text); 43 43 case 'submit': 44 - return color.green; 44 + return (text: string) => styleText('green', text); 45 45 default: 46 - return color.magenta; 46 + return (text: string) => styleText('magenta', text); 47 47 } 48 48 }; 49 49 const drawProgress = (state: State, msg: string) => { 50 50 const active = Math.floor((value / max) * size); 51 - return `${activeStyle(state)(S_PROGRESS_CHAR[style].repeat(active))}${color.dim(S_PROGRESS_CHAR[style].repeat(size - active))} ${msg}`; 51 + return `${activeStyle(state)(S_PROGRESS_CHAR[style].repeat(active))}${styleText('dim', S_PROGRESS_CHAR[style].repeat(size - active))} ${msg}`; 52 52 }; 53 53 54 54 const start = (msg = '') => {
+13 -13
packages/prompts/src/select-key.ts
··· 1 + import { styleText } from 'node:util'; 1 2 import { SelectKeyPrompt, settings, wrapTextWithPrefix } from '@clack/core'; 2 - import color from 'picocolors'; 3 3 import { type CommonOptions, S_BAR, S_BAR_END, symbol } from './common.js'; 4 4 import type { Option } from './select.js'; 5 5 ··· 17 17 ) => { 18 18 const label = option.label ?? String(option.value); 19 19 if (state === 'selected') { 20 - return `${color.dim(label)}`; 20 + return `${styleText('dim', label)}`; 21 21 } 22 22 if (state === 'cancelled') { 23 - return `${color.strikethrough(color.dim(label))}`; 23 + return `${styleText(['strikethrough', 'dim'], label)}`; 24 24 } 25 25 if (state === 'active') { 26 - return `${color.bgCyan(color.gray(` ${option.value} `))} ${label}${ 27 - option.hint ? ` ${color.dim(`(${option.hint})`)}` : '' 26 + return `${styleText(['bgCyan', 'gray'], ` ${option.value} `)} ${label}${ 27 + option.hint ? ` ${styleText('dim', `(${option.hint})`)}` : '' 28 28 }`; 29 29 } 30 - return `${color.gray(color.bgWhite(color.inverse(` ${option.value} `)))} ${label}${ 31 - option.hint ? ` ${color.dim(`(${option.hint})`)}` : '' 30 + return `${styleText(['gray', 'bgWhite', 'inverse'], ` ${option.value} `)} ${label}${ 31 + option.hint ? ` ${styleText('dim', `(${option.hint})`)}` : '' 32 32 }`; 33 33 }; 34 34 ··· 41 41 caseSensitive: opts.caseSensitive, 42 42 render() { 43 43 const hasGuide = opts.withGuide ?? settings.withGuide; 44 - const title = `${hasGuide ? `${color.gray(S_BAR)}\n` : ''}${symbol(this.state)} ${opts.message}\n`; 44 + const title = `${hasGuide ? `${styleText('gray', S_BAR)}\n` : ''}${symbol(this.state)} ${opts.message}\n`; 45 45 46 46 switch (this.state) { 47 47 case 'submit': { 48 - const submitPrefix = hasGuide ? `${color.gray(S_BAR)} ` : ''; 48 + const submitPrefix = hasGuide ? `${styleText('gray', S_BAR)} ` : ''; 49 49 const selectedOption = 50 50 this.options.find((opt) => opt.value === this.value) ?? opts.options[0]; 51 51 const wrapped = wrapTextWithPrefix( ··· 56 56 return `${title}${wrapped}`; 57 57 } 58 58 case 'cancel': { 59 - const cancelPrefix = hasGuide ? `${color.gray(S_BAR)} ` : ''; 59 + const cancelPrefix = hasGuide ? `${styleText('gray', S_BAR)} ` : ''; 60 60 const wrapped = wrapTextWithPrefix( 61 61 opts.output, 62 62 opt(this.options[0], 'cancelled'), 63 63 cancelPrefix 64 64 ); 65 - return `${title}${wrapped}${hasGuide ? `\n${color.gray(S_BAR)}` : ''}`; 65 + return `${title}${wrapped}${hasGuide ? `\n${styleText('gray', S_BAR)}` : ''}`; 66 66 } 67 67 default: { 68 - const defaultPrefix = hasGuide ? `${color.cyan(S_BAR)} ` : ''; 69 - const defaultPrefixEnd = hasGuide ? color.cyan(S_BAR_END) : ''; 68 + const defaultPrefix = hasGuide ? `${styleText('cyan', S_BAR)} ` : ''; 69 + const defaultPrefixEnd = hasGuide ? styleText('cyan', S_BAR_END) : ''; 70 70 const wrapped = this.options 71 71 .map((option, i) => 72 72 wrapTextWithPrefix(
+14 -14
packages/prompts/src/select.ts
··· 1 + import { styleText } from 'node:util'; 1 2 import { SelectPrompt, settings, wrapTextWithPrefix } from '@clack/core'; 2 - import color from 'picocolors'; 3 3 import { 4 4 type CommonOptions, 5 5 S_BAR, ··· 90 90 const label = option.label ?? String(option.value); 91 91 switch (state) { 92 92 case 'disabled': 93 - return `${color.gray(S_RADIO_INACTIVE)} ${computeLabel(label, color.gray)}${ 94 - option.hint ? ` ${color.dim(`(${option.hint ?? 'disabled'})`)}` : '' 93 + return `${styleText('gray', S_RADIO_INACTIVE)} ${computeLabel(label, (text) => styleText('gray', text))}${ 94 + option.hint ? ` ${styleText('dim', `(${option.hint ?? 'disabled'})`)}` : '' 95 95 }`; 96 96 case 'selected': 97 - return `${computeLabel(label, color.dim)}`; 97 + return `${computeLabel(label, (text) => styleText('dim', text))}`; 98 98 case 'active': 99 - return `${color.green(S_RADIO_ACTIVE)} ${label}${ 100 - option.hint ? ` ${color.dim(`(${option.hint})`)}` : '' 99 + return `${styleText('green', S_RADIO_ACTIVE)} ${label}${ 100 + option.hint ? ` ${styleText('dim', `(${option.hint})`)}` : '' 101 101 }`; 102 102 case 'cancelled': 103 - return `${computeLabel(label, (str) => color.strikethrough(color.dim(str)))}`; 103 + return `${computeLabel(label, (str) => styleText(['strikethrough', 'dim'], str))}`; 104 104 default: 105 - return `${color.dim(S_RADIO_INACTIVE)} ${computeLabel(label, color.dim)}`; 105 + return `${styleText('dim', S_RADIO_INACTIVE)} ${computeLabel(label, (text) => styleText('dim', text))}`; 106 106 } 107 107 }; 108 108 ··· 122 122 titlePrefixBar, 123 123 titlePrefix 124 124 ); 125 - const title = `${hasGuide ? `${color.gray(S_BAR)}\n` : ''}${messageLines}\n`; 125 + const title = `${hasGuide ? `${styleText('gray', S_BAR)}\n` : ''}${messageLines}\n`; 126 126 127 127 switch (this.state) { 128 128 case 'submit': { 129 - const submitPrefix = hasGuide ? `${color.gray(S_BAR)} ` : ''; 129 + const submitPrefix = hasGuide ? `${styleText('gray', S_BAR)} ` : ''; 130 130 const wrappedLines = wrapTextWithPrefix( 131 131 opts.output, 132 132 opt(this.options[this.cursor], 'selected'), ··· 135 135 return `${title}${wrappedLines}`; 136 136 } 137 137 case 'cancel': { 138 - const cancelPrefix = hasGuide ? `${color.gray(S_BAR)} ` : ''; 138 + const cancelPrefix = hasGuide ? `${styleText('gray', S_BAR)} ` : ''; 139 139 const wrappedLines = wrapTextWithPrefix( 140 140 opts.output, 141 141 opt(this.options[this.cursor], 'cancelled'), 142 142 cancelPrefix 143 143 ); 144 - return `${title}${wrappedLines}${hasGuide ? `\n${color.gray(S_BAR)}` : ''}`; 144 + return `${title}${wrappedLines}${hasGuide ? `\n${styleText('gray', S_BAR)}` : ''}`; 145 145 } 146 146 default: { 147 - const prefix = hasGuide ? `${color.cyan(S_BAR)} ` : ''; 148 - const prefixEnd = hasGuide ? color.cyan(S_BAR_END) : ''; 147 + const prefix = hasGuide ? `${styleText('cyan', S_BAR)} ` : ''; 148 + const prefixEnd = hasGuide ? styleText('cyan', S_BAR_END) : ''; 149 149 // Calculate rowPadding: title lines + footer lines (S_BAR_END + trailing newline) 150 150 const titleLineCount = title.split('\n').length; 151 151 const footerLineCount = hasGuide ? 2 : 1; // S_BAR_END + trailing newline (or just trailing newline)
+6 -6
packages/prompts/src/spinner.ts
··· 1 + import { styleText } from 'node:util'; 1 2 import { block, getColumns, settings } from '@clack/core'; 2 3 import { wrapAnsi } from 'fast-wrap-ansi'; 3 - import color from 'picocolors'; 4 4 import { cursor, erase } from 'sisteransi'; 5 5 import { 6 6 type CommonOptions, ··· 32 32 readonly isCancelled: boolean; 33 33 } 34 34 35 - const defaultStyleFn: SpinnerOptions['styleFrame'] = color.magenta; 35 + const defaultStyleFn: SpinnerOptions['styleFrame'] = (frame) => styleText('magenta', frame); 36 36 37 37 export const spinner = ({ 38 38 indicator = 'dots', ··· 135 135 _message = removeTrailingDots(msg); 136 136 _origin = performance.now(); 137 137 if (hasGuide) { 138 - output.write(`${color.gray(S_BAR)}\n`); 138 + output.write(`${styleText('gray', S_BAR)}\n`); 139 139 } 140 140 let frameIndex = 0; 141 141 let indicatorTimer = 0; ··· 177 177 clearPrevMessage(); 178 178 const step = 179 179 code === 0 180 - ? color.green(S_STEP_SUBMIT) 180 + ? styleText('green', S_STEP_SUBMIT) 181 181 : code === 1 182 - ? color.red(S_STEP_CANCEL) 183 - : color.red(S_STEP_ERROR); 182 + ? styleText('red', S_STEP_CANCEL) 183 + : styleText('red', S_STEP_ERROR); 184 184 _message = msg ?? _message; 185 185 if (!silent) { 186 186 if (indicator === 'timer') {
+9 -10
packages/prompts/src/stream.ts
··· 1 - import { stripVTControlCharacters as strip } from 'node:util'; 2 - import color from 'picocolors'; 1 + import { stripVTControlCharacters as strip, styleText } from 'node:util'; 3 2 import { S_BAR, S_ERROR, S_INFO, S_STEP_SUBMIT, S_SUCCESS, S_WARN } from './common.js'; 4 3 import type { LogMessageOptions } from './log.js'; 5 4 6 - const prefix = `${color.gray(S_BAR)} `; 5 + const prefix = `${styleText('gray', S_BAR)} `; 7 6 8 7 // TODO (43081j): this currently doesn't support custom `output` writables 9 8 // because we rely on `columns` existing (i.e. `process.stdout.columns). ··· 13 12 export const stream = { 14 13 message: async ( 15 14 iterable: Iterable<string> | AsyncIterable<string>, 16 - { symbol = color.gray(S_BAR) }: LogMessageOptions = {} 15 + { symbol = styleText('gray', S_BAR) }: LogMessageOptions = {} 17 16 ) => { 18 - process.stdout.write(`${color.gray(S_BAR)}\n${symbol} `); 17 + process.stdout.write(`${styleText('gray', S_BAR)}\n${symbol} `); 19 18 let lineWidth = 3; 20 19 for await (let chunk of iterable) { 21 20 chunk = chunk.replace(/\n/g, `\n${prefix}`); ··· 34 33 process.stdout.write('\n'); 35 34 }, 36 35 info: (iterable: Iterable<string> | AsyncIterable<string>) => { 37 - return stream.message(iterable, { symbol: color.blue(S_INFO) }); 36 + return stream.message(iterable, { symbol: styleText('blue', S_INFO) }); 38 37 }, 39 38 success: (iterable: Iterable<string> | AsyncIterable<string>) => { 40 - return stream.message(iterable, { symbol: color.green(S_SUCCESS) }); 39 + return stream.message(iterable, { symbol: styleText('green', S_SUCCESS) }); 41 40 }, 42 41 step: (iterable: Iterable<string> | AsyncIterable<string>) => { 43 - return stream.message(iterable, { symbol: color.green(S_STEP_SUBMIT) }); 42 + return stream.message(iterable, { symbol: styleText('green', S_STEP_SUBMIT) }); 44 43 }, 45 44 warn: (iterable: Iterable<string> | AsyncIterable<string>) => { 46 - return stream.message(iterable, { symbol: color.yellow(S_WARN) }); 45 + return stream.message(iterable, { symbol: styleText('yellow', S_WARN) }); 47 46 }, 48 47 /** alias for `log.warn()`. */ 49 48 warning: (iterable: Iterable<string> | AsyncIterable<string>) => { 50 49 return stream.warn(iterable); 51 50 }, 52 51 error: (iterable: Iterable<string> | AsyncIterable<string>) => { 53 - return stream.message(iterable, { symbol: color.red(S_ERROR) }); 52 + return stream.message(iterable, { symbol: styleText('red', S_ERROR) }); 54 53 }, 55 54 };
+19 -13
packages/prompts/src/task-log.ts
··· 1 1 import type { Writable } from 'node:stream'; 2 + import { styleText } from 'node:util'; 2 3 import { getColumns } from '@clack/core'; 3 - import color from 'picocolors'; 4 4 import { erase } from 'sisteransi'; 5 5 import { 6 6 type CommonOptions, ··· 47 47 export const taskLog = (opts: TaskLogOptions) => { 48 48 const output: Writable = opts.output ?? process.stdout; 49 49 const columns = getColumns(output); 50 - const secondarySymbol = color.gray(S_BAR); 50 + const secondarySymbol = styleText('gray', S_BAR); 51 51 const spacing = opts.spacing ?? 1; 52 52 const barSize = 3; 53 53 const retainLog = opts.retainLog === true; 54 54 const isTTY = !isCIFn() && isTTYFn(output); 55 55 56 56 output.write(`${secondarySymbol}\n`); 57 - output.write(`${color.green(S_STEP_SUBMIT)} ${opts.title}\n`); 57 + output.write(`${styleText('green', S_STEP_SUBMIT)} ${opts.title}\n`); 58 58 for (let i = 0; i < spacing; i++) { 59 59 output.write(`${secondarySymbol}\n`); 60 60 } ··· 108 108 const printBuffer = (buffer: BufferEntry, messageSpacing?: number, full?: boolean): void => { 109 109 const messages = full ? `${buffer.full}\n${buffer.value}` : buffer.value; 110 110 if (buffer.header !== undefined && buffer.header !== '') { 111 - log.message(buffer.header.split('\n').map(color.bold), { 111 + log.message( 112 + buffer.header.split('\n').map((line) => styleText('bold', line)), 113 + { 114 + output, 115 + secondarySymbol, 116 + symbol: secondarySymbol, 117 + spacing: 0, 118 + } 119 + ); 120 + } 121 + log.message( 122 + messages.split('\n').map((line) => styleText('dim', line)), 123 + { 112 124 output, 113 125 secondarySymbol, 114 126 symbol: secondarySymbol, 115 - spacing: 0, 116 - }); 117 - } 118 - log.message(messages.split('\n').map(color.dim), { 119 - output, 120 - secondarySymbol, 121 - symbol: secondarySymbol, 122 - spacing: messageSpacing ?? spacing, 123 - }); 127 + spacing: messageSpacing ?? spacing, 128 + } 129 + ); 124 130 }; 125 131 const renderBuffer = (): void => { 126 132 for (const buffer of buffers) {
+13 -13
packages/prompts/src/text.ts
··· 1 + import { styleText } from 'node:util'; 1 2 import { settings, TextPrompt } from '@clack/core'; 2 - import color from 'picocolors'; 3 3 import { type CommonOptions, S_BAR, S_BAR_END, symbol } from './common.js'; 4 4 5 5 export interface TextOptions extends CommonOptions { ··· 21 21 input: opts.input, 22 22 render() { 23 23 const hasGuide = opts?.withGuide ?? settings.withGuide; 24 - const titlePrefix = `${hasGuide ? `${color.gray(S_BAR)}\n` : ''}${symbol(this.state)} `; 24 + const titlePrefix = `${hasGuide ? `${styleText('gray', S_BAR)}\n` : ''}${symbol(this.state)} `; 25 25 const title = `${titlePrefix}${opts.message}\n`; 26 26 const placeholder = opts.placeholder 27 - ? color.inverse(opts.placeholder[0]) + color.dim(opts.placeholder.slice(1)) 28 - : color.inverse(color.hidden('_')); 27 + ? styleText('inverse', opts.placeholder[0]) + styleText('dim', opts.placeholder.slice(1)) 28 + : styleText(['inverse', 'hidden'], '_'); 29 29 const userInput = !this.userInput ? placeholder : this.userInputWithCursor; 30 30 const value = this.value ?? ''; 31 31 32 32 switch (this.state) { 33 33 case 'error': { 34 - const errorText = this.error ? ` ${color.yellow(this.error)}` : ''; 35 - const errorPrefix = hasGuide ? `${color.yellow(S_BAR)} ` : ''; 36 - const errorPrefixEnd = hasGuide ? color.yellow(S_BAR_END) : ''; 34 + const errorText = this.error ? ` ${styleText('yellow', this.error)}` : ''; 35 + const errorPrefix = hasGuide ? `${styleText('yellow', S_BAR)} ` : ''; 36 + const errorPrefixEnd = hasGuide ? styleText('yellow', S_BAR_END) : ''; 37 37 return `${title.trim()}\n${errorPrefix}${userInput}\n${errorPrefixEnd}${errorText}\n`; 38 38 } 39 39 case 'submit': { 40 - const valueText = value ? ` ${color.dim(value)}` : ''; 41 - const submitPrefix = hasGuide ? color.gray(S_BAR) : ''; 40 + const valueText = value ? ` ${styleText('dim', value)}` : ''; 41 + const submitPrefix = hasGuide ? styleText('gray', S_BAR) : ''; 42 42 return `${title}${submitPrefix}${valueText}`; 43 43 } 44 44 case 'cancel': { 45 - const valueText = value ? ` ${color.strikethrough(color.dim(value))}` : ''; 46 - const cancelPrefix = hasGuide ? color.gray(S_BAR) : ''; 45 + const valueText = value ? ` ${styleText(['strikethrough', 'dim'], value)}` : ''; 46 + const cancelPrefix = hasGuide ? styleText('gray', S_BAR) : ''; 47 47 return `${title}${cancelPrefix}${valueText}${value.trim() ? `\n${cancelPrefix}` : ''}`; 48 48 } 49 49 default: { 50 - const defaultPrefix = hasGuide ? `${color.cyan(S_BAR)} ` : ''; 51 - const defaultPrefixEnd = hasGuide ? color.cyan(S_BAR_END) : ''; 50 + const defaultPrefix = hasGuide ? `${styleText('cyan', S_BAR)} ` : ''; 51 + const defaultPrefixEnd = hasGuide ? styleText('cyan', S_BAR_END) : ''; 52 52 return `${title}${defaultPrefix}${userInput}\n${defaultPrefixEnd}\n`; 53 53 } 54 54 }
+2 -2
packages/prompts/test/box.test.ts
··· 1 + import { styleText } from 'node:util'; 1 2 import { updateSettings } from '@clack/core'; 2 - import colors from 'picocolors'; 3 3 import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test, vi } from 'vitest'; 4 4 import * as prompts from '../src/index.js'; 5 5 import { MockReadable, MockWritable } from './test-utils.js'; ··· 243 243 input, 244 244 output, 245 245 width: 'auto', 246 - formatBorder: colors.red, 246 + formatBorder: (text: string) => styleText('red', text), 247 247 }); 248 248 249 249 expect(output.buffer).toMatchSnapshot();
+25 -12
packages/prompts/test/limit-options.test.ts
··· 1 - import color from 'picocolors'; 1 + import { styleText } from 'node:util'; 2 2 import { beforeEach, describe, expect, test } from 'vitest'; 3 3 import { type LimitOptionsParams, limitOptions } from '../src/index.js'; 4 4 import { MockWritable } from './test-utils.js'; ··· 39 39 ]; 40 40 options.maxItems = 3; 41 41 const result = limitOptions(options); 42 - expect(result).toEqual(['Item 1', 'Item 2', 'Item 3', 'Item 4', color.dim('...')]); 42 + expect(result).toEqual(['Item 1', 'Item 2', 'Item 3', 'Item 4', styleText('dim', '...')]); 43 43 }); 44 44 45 45 test('returns sliding window when cursor moves down', async () => { ··· 59 59 options.maxItems = 5; 60 60 options.cursor = 6; 61 61 const result = limitOptions(options); 62 - expect(result).toEqual([color.dim('...'), 'Item 6', 'Item 7', 'Item 8', color.dim('...')]); 62 + expect(result).toEqual([ 63 + styleText('dim', '...'), 64 + 'Item 6', 65 + 'Item 7', 66 + 'Item 8', 67 + styleText('dim', '...'), 68 + ]); 63 69 }); 64 70 65 71 test('returns sliding window near end of list', async () => { ··· 78 84 options.maxItems = 5; 79 85 options.cursor = 8; 80 86 const result = limitOptions(options); 81 - expect(result).toEqual([color.dim('...'), 'Item 7', 'Item 8', 'Item 9', 'Item 10']); 87 + expect(result).toEqual([styleText('dim', '...'), 'Item 7', 'Item 8', 'Item 9', 'Item 10']); 82 88 }); 83 89 84 90 test('handles empty options list', async () => { ··· 103 109 output.rows = 7; 104 110 options.maxItems = 10; 105 111 const result = limitOptions(options); 106 - expect(result).toEqual(['Item 1', 'Item 2', color.dim('...')]); 112 + expect(result).toEqual(['Item 1', 'Item 2', styleText('dim', '...')]); 107 113 }); 108 114 109 115 test('handle multi-line item clamping (start)', async () => { ··· 138 144 'Item 6', 139 145 'Item 7', 140 146 'Item 8', 141 - color.dim('...'), 147 + styleText('dim', '...'), 142 148 ]); 143 149 }); 144 150 ··· 164 170 options.cursor = 7; 165 171 const result = limitOptions(options); 166 172 expect(result).toEqual([ 167 - color.dim('...'), 173 + styleText('dim', '...'), 168 174 'Item 2', 169 175 'Item 3', 170 176 'Item 4', ··· 175 181 'Item 6', 176 182 'Item 7', 177 183 'Item 8', 178 - color.dim('...'), 184 + styleText('dim', '...'), 179 185 ]); 180 186 }); 181 187 ··· 201 207 options.cursor = 9; 202 208 const result = limitOptions(options); 203 209 expect(result).toEqual([ 204 - color.dim('...'), 210 + styleText('dim', '...'), 205 211 'Item 4', 206 212 'Item 5', 207 213 'Item 6', ··· 259 265 options.rowPadding = 6; 260 266 // Available rows for options = 12 - 6 = 6 261 267 const result = limitOptions(options); 262 - expect(result).toEqual(['Item 1', 'Item 2', 'Item 3', 'Item 4', 'Item 5', color.dim('...')]); 268 + expect(result).toEqual([ 269 + 'Item 1', 270 + 'Item 2', 271 + 'Item 3', 272 + 'Item 4', 273 + 'Item 5', 274 + styleText('dim', '...'), 275 + ]); 263 276 }); 264 277 265 278 test('respects custom rowPadding when scrolling', async () => { ··· 283 296 // Available rows for options = 12 - 6 = 6 284 297 const result = limitOptions(options); 285 298 expect(result).toEqual([ 286 - color.dim('...'), 299 + styleText('dim', '...'), 287 300 'Item 4', 288 301 'Item 5', 289 302 'Item 6', 290 303 'Item 7', 291 - color.dim('...'), 304 + styleText('dim', '...'), 292 305 ]); 293 306 }); 294 307 });
+3 -3
packages/prompts/test/log.test.ts
··· 1 - import colors from 'picocolors'; 1 + import { styleText } from 'node:util'; 2 2 import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test, vi } from 'vitest'; 3 3 import * as prompts from '../src/index.js'; 4 4 import { MockWritable } from './test-utils.js'; ··· 51 51 52 52 test('renders message with custom symbols and spacing', () => { 53 53 prompts.log.message('custom\nsymbols', { 54 - symbol: colors.red('>>'), 55 - secondarySymbol: colors.yellow('--'), 54 + symbol: styleText('red', '>>'), 55 + secondarySymbol: styleText('yellow', '--'), 56 56 output, 57 57 }); 58 58
+4 -4
packages/prompts/test/note.test.ts
··· 1 - import colors from 'picocolors'; 1 + import { styleText } from 'node:util'; 2 2 import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test, vi } from 'vitest'; 3 3 import * as prompts from '../src/index.js'; 4 4 import { MockReadable, MockWritable } from './test-utils.js'; ··· 56 56 57 57 test('formatter which adds colors works', () => { 58 58 prompts.note('line 0\nline 1\nline 2', 'title', { 59 - format: (line) => colors.red(line), 59 + format: (line) => styleText('red', line), 60 60 input, 61 61 output, 62 62 }); ··· 79 79 const message = `${'test string '.repeat(32)}\n`.repeat(4).trim(); 80 80 output.columns = 75; 81 81 prompts.note(message, 'title', { 82 - format: (line) => colors.red(`* ${colors.cyan(line)} *`), 82 + format: (line) => styleText('red', `* ${styleText('cyan', line)} *`), 83 83 input, 84 84 output, 85 85 }); ··· 102 102 const messages = ['이게 첫 번째 줄이에요', 'これは次の行です']; 103 103 output.columns = 10; 104 104 prompts.note(messages.join('\n'), '这是标题', { 105 - format: (line) => colors.red(`* ${colors.cyan(line)} *`), 105 + format: (line) => styleText('red', `* ${styleText('cyan', line)} *`), 106 106 input, 107 107 output, 108 108 });
+2 -2
packages/prompts/test/spinner.test.ts
··· 1 1 import { EventEmitter } from 'node:stream'; 2 + import { styleText } from 'node:util'; 2 3 import { getColumns, updateSettings } from '@clack/core'; 3 - import color from 'picocolors'; 4 4 import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test, vi } from 'vitest'; 5 5 import * as prompts from '../src/index.js'; 6 6 import { MockWritable } from './test-utils.js'; ··· 261 261 }); 262 262 263 263 test('custom frame style', () => { 264 - const result = prompts.spinner({ output, styleFrame: color.red }); 264 + const result = prompts.spinner({ output, styleFrame: (text) => styleText('red', text) }); 265 265 266 266 result.start(); 267 267
-6
pnpm-lock.yaml
··· 60 60 61 61 packages/core: 62 62 dependencies: 63 - picocolors: 64 - specifier: ^1.0.0 65 - version: 1.1.1 66 63 sisteransi: 67 64 specifier: ^1.0.5 68 65 version: 1.0.5 ··· 79 76 '@clack/core': 80 77 specifier: workspace:* 81 78 version: link:../core 82 - picocolors: 83 - specifier: ^1.0.0 84 - version: 1.1.1 85 79 sisteransi: 86 80 specifier: ^1.0.5 87 81 version: 1.0.5