diff --git a/.changeset/rotten-jokes-read.md b/.changeset/rotten-jokes-read.md new file mode 100644 index 00000000..3dd9f740 --- /dev/null +++ b/.changeset/rotten-jokes-read.md @@ -0,0 +1,5 @@ +--- +"@clack/prompts": patch +--- + +Add `withGuide` support to password prompt. diff --git a/packages/prompts/src/password.ts b/packages/prompts/src/password.ts index 8010960b..838a3b19 100644 --- a/packages/prompts/src/password.ts +++ b/packages/prompts/src/password.ts @@ -1,4 +1,4 @@ -import { PasswordPrompt } from '@clack/core'; +import { PasswordPrompt, settings } from '@clack/core'; import color from 'picocolors'; import { type CommonOptions, S_BAR, S_BAR_END, S_PASSWORD_MASK, symbol } from './common.js'; @@ -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 hasGuide = (opts.withGuide ?? settings.withGuide) !== false; + const title = `${hasGuide ? `${color.gray(S_BAR)}\n` : ''}${symbol(this.state)} ${opts.message}\n`; const userInput = this.userInputWithCursor; const masked = this.masked; @@ -26,22 +27,27 @@ export const password = (opts: PasswordOptions) => { if (opts.clearOnError) { this.clear(); } - return `${title.trim()}\n${color.yellow(S_BAR)}${maskedText}\n${color.yellow( - S_BAR_END - )} ${color.yellow(this.error)}\n`; + const errorPrefix = hasGuide ? `${color.yellow(S_BAR)}` : ''; + const errorPrefixEnd = hasGuide ? color.yellow(S_BAR_END) : ''; + return `${title.trim()}\n${errorPrefix}${maskedText}\n${errorPrefixEnd} ${color.yellow(this.error)}\n`; } case 'submit': { const maskedText = masked ? ` ${color.dim(masked)}` : ''; - return `${title}${color.gray(S_BAR)}${maskedText}`; + const submitPrefix = hasGuide ? color.gray(S_BAR) : ''; + return `${title}${submitPrefix}${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 cancelPrefix = hasGuide ? color.gray(S_BAR) : ''; + return `${title}${cancelPrefix}${maskedText}${ + masked && hasGuide ? `\n${color.gray(S_BAR)}` : '' }`; } - default: - return `${title}${color.cyan(S_BAR)} ${userInput}\n${color.cyan(S_BAR_END)}\n`; + default: { + const defaultPrefix = hasGuide ? `${color.cyan(S_BAR)} ` : ''; + const defaultPrefixEnd = hasGuide ? color.cyan(S_BAR_END) : ''; + return `${title}${defaultPrefix}${userInput}\n${defaultPrefixEnd}\n`; + } } }, }).prompt() as Promise; diff --git a/packages/prompts/test/__snapshots__/password.test.ts.snap b/packages/prompts/test/__snapshots__/password.test.ts.snap index fbe7c7ca..42e53235 100644 --- a/packages/prompts/test/__snapshots__/password.test.ts.snap +++ b/packages/prompts/test/__snapshots__/password.test.ts.snap @@ -57,6 +57,23 @@ exports[`password (isCI = false) > clears input on error when clearOnError is tr ] `; +exports[`password (isCI = false) > global withGuide: false removes guide 1`] = ` +[ + "", + "◆ foo +_ + +", + "", + "", + "◇ foo +", + " +", + "", +] +`; + exports[`password (isCI = false) > renders and clears validation errors 1`] = ` [ "", @@ -197,6 +214,23 @@ exports[`password (isCI = false) > renders message 1`] = ` ] `; +exports[`password (isCI = false) > withGuide: false removes guide 1`] = ` +[ + "", + "◆ foo +_ + +", + "", + "", + "◇ foo +", + " +", + "", +] +`; + exports[`password (isCI = true) > can be aborted by a signal 1`] = ` [ "", @@ -254,6 +288,23 @@ exports[`password (isCI = true) > clears input on error when clearOnError is tru ] `; +exports[`password (isCI = true) > global withGuide: false removes guide 1`] = ` +[ + "", + "◆ foo +_ + +", + "", + "", + "◇ foo +", + " +", + "", +] +`; + exports[`password (isCI = true) > renders and clears validation errors 1`] = ` [ "", @@ -393,3 +444,20 @@ exports[`password (isCI = true) > renders message 1`] = ` "", ] `; + +exports[`password (isCI = true) > withGuide: false removes guide 1`] = ` +[ + "", + "◆ foo +_ + +", + "", + "", + "◇ foo +", + " +", + "", +] +`; diff --git a/packages/prompts/test/password.test.ts b/packages/prompts/test/password.test.ts index 4db940ec..536b3b41 100644 --- a/packages/prompts/test/password.test.ts +++ b/packages/prompts/test/password.test.ts @@ -1,3 +1,4 @@ +import { updateSettings } from '@clack/core'; 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'; @@ -23,6 +24,7 @@ describe.each(['true', 'false'])('password (isCI = %s)', (isCI) => { afterEach(() => { vi.restoreAllMocks(); + updateSettings({ withGuide: true }); }); test('renders message', async () => { @@ -149,4 +151,35 @@ describe.each(['true', 'false'])('password (isCI = %s)', (isCI) => { expect(value).toBe('yz'); expect(output.buffer).toMatchSnapshot(); }); + + test('withGuide: false removes guide', async () => { + const result = prompts.password({ + message: 'foo', + withGuide: false, + input, + output, + }); + + input.emit('keypress', '', { name: 'return' }); + + await result; + + expect(output.buffer).toMatchSnapshot(); + }); + + test('global withGuide: false removes guide', async () => { + updateSettings({ withGuide: false }); + + const result = prompts.password({ + message: 'foo', + input, + output, + }); + + input.emit('keypress', '', { name: 'return' }); + + await result; + + expect(output.buffer).toMatchSnapshot(); + }); });