[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.

feat: add param to clear input after password prompt error (#364)

authored by

kajoseph and committed by
GitHub
(Aug 11, 2025, 4:44 PM +0100) 1604f974 4c89dd7e

+127 -1
+6
.changeset/solid-goats-shop.md
··· 1 + --- 2 + "@clack/prompts": minor 3 + "@clack/core": minor 4 + --- 5 + 6 + Add `clearOnError` option to password prompt to automatically clear input when validation fails
+3
packages/core/src/prompts/password.ts
··· 25 25 const s2 = masked.slice(this.cursor); 26 26 return `${s1}${color.inverse(s2[0])}${s2.slice(1)}`; 27 27 } 28 + clear() { 29 + this._clearUserInput(); 30 + } 28 31 constructor({ mask, ...opts }: PasswordOptions) { 29 32 super(opts); 30 33 this._mask = mask ?? '•';
+5
packages/core/src/prompts/prompt.ts
··· 186 186 } 187 187 } 188 188 189 + protected _clearUserInput(): void { 190 + this.rl?.write(null, { ctrl: true, name: 'u' }); 191 + this._setUserInput(''); 192 + } 193 + 189 194 private onKeypress(char: string | undefined, key: Key) { 190 195 if (this._track && key.name !== 'return') { 191 196 if (key.name && this._isActionKey(char, key)) {
+4
packages/prompts/src/password.ts
··· 6 6 message: string; 7 7 mask?: string; 8 8 validate?: (value: string | undefined) => string | Error | undefined; 9 + clearOnError?: boolean; 9 10 } 10 11 export const password = (opts: PasswordOptions) => { 11 12 return new PasswordPrompt({ ··· 22 23 switch (this.state) { 23 24 case 'error': { 24 25 const maskedText = masked ? ` ${masked}` : ''; 26 + if (opts.clearOnError) { 27 + this.clear(); 28 + } 25 29 return `${title.trim()}\n${color.yellow(S_BAR)}${maskedText}\n${color.yellow( 26 30 S_BAR_END 27 31 )} ${color.yellow(this.error)}\n`;
+86
packages/prompts/test/__snapshots__/password.test.ts.snap
··· 14 14 ] 15 15 `; 16 16 17 + exports[`password (isCI = false) > clears input on error when clearOnError is true 1`] = ` 18 + [ 19 + "<cursor.hide>", 20 + "│ 21 + ◆ foo 22 + │ _ 23 + └ 24 + ", 25 + "<cursor.backward count=999><cursor.up count=4>", 26 + "<cursor.down count=2>", 27 + "<erase.line><cursor.left count=1>", 28 + "│ ▪_", 29 + "<cursor.down count=2>", 30 + "<cursor.backward count=999><cursor.up count=4>", 31 + "<cursor.down count=1>", 32 + "<erase.down>", 33 + "▲ foo 34 + │ ▪ 35 + └ Error 36 + ", 37 + "<cursor.backward count=999><cursor.up count=4>", 38 + "<cursor.down count=1>", 39 + "<erase.down>", 40 + "◆ foo 41 + │ ▪_ 42 + └ 43 + ", 44 + "<cursor.backward count=999><cursor.up count=4>", 45 + "<cursor.down count=2>", 46 + "<erase.line><cursor.left count=1>", 47 + "│ ▪▪_", 48 + "<cursor.down count=2>", 49 + "<cursor.backward count=999><cursor.up count=4>", 50 + "<cursor.down count=1>", 51 + "<erase.down>", 52 + "◇ foo 53 + │ ▪▪", 54 + " 55 + ", 56 + "<cursor.show>", 57 + ] 58 + `; 59 + 17 60 exports[`password (isCI = false) > renders and clears validation errors 1`] = ` 18 61 [ 19 62 "<cursor.hide>", ··· 162 205 │ _ 163 206 └ 164 207 ", 208 + " 209 + ", 210 + "<cursor.show>", 211 + ] 212 + `; 213 + 214 + exports[`password (isCI = true) > clears input on error when clearOnError is true 1`] = ` 215 + [ 216 + "<cursor.hide>", 217 + "│ 218 + ◆ foo 219 + │ _ 220 + └ 221 + ", 222 + "<cursor.backward count=999><cursor.up count=4>", 223 + "<cursor.down count=2>", 224 + "<erase.line><cursor.left count=1>", 225 + "│ ▪_", 226 + "<cursor.down count=2>", 227 + "<cursor.backward count=999><cursor.up count=4>", 228 + "<cursor.down count=1>", 229 + "<erase.down>", 230 + "▲ foo 231 + │ ▪ 232 + └ Error 233 + ", 234 + "<cursor.backward count=999><cursor.up count=4>", 235 + "<cursor.down count=1>", 236 + "<erase.down>", 237 + "◆ foo 238 + │ ▪_ 239 + └ 240 + ", 241 + "<cursor.backward count=999><cursor.up count=4>", 242 + "<cursor.down count=2>", 243 + "<erase.line><cursor.left count=1>", 244 + "│ ▪▪_", 245 + "<cursor.down count=2>", 246 + "<cursor.backward count=999><cursor.up count=4>", 247 + "<cursor.down count=1>", 248 + "<erase.down>", 249 + "◇ foo 250 + │ ▪▪", 165 251 " 166 252 ", 167 253 "<cursor.show>",
+23 -1
packages/prompts/test/password.test.ts
··· 92 92 input.emit('keypress', 'y', { name: 'y' }); 93 93 input.emit('keypress', '', { name: 'return' }); 94 94 95 - await result; 95 + const value = await result; 96 96 97 + expect(value).toBe('xy'); 97 98 expect(output.buffer).toMatchSnapshot(); 98 99 }); 99 100 ··· 125 126 controller.abort(); 126 127 const value = await result; 127 128 expect(prompts.isCancel(value)).toBe(true); 129 + expect(output.buffer).toMatchSnapshot(); 130 + }); 131 + 132 + test('clears input on error when clearOnError is true', async () => { 133 + const result = prompts.password({ 134 + message: 'foo', 135 + input, 136 + output, 137 + validate: (v) => (v === 'yz' ? undefined : 'Error'), 138 + clearOnError: true, 139 + }); 140 + 141 + input.emit('keypress', 'x', { name: 'x' }); 142 + input.emit('keypress', '', { name: 'return' }); 143 + input.emit('keypress', 'y', { name: 'y' }); 144 + input.emit('keypress', 'z', { name: 'z' }); 145 + input.emit('keypress', '', { name: 'return' }); 146 + 147 + const value = await result; 148 + 149 + expect(value).toBe('yz'); 128 150 expect(output.buffer).toMatchSnapshot(); 129 151 }); 130 152 });