Skip to content

Commit acb814b

Browse files
authored
Merge pull request #58 from constructive-io/devin/1769267254-inquirerer-password-type
feat(inquirerer): add password input type with masked display
2 parents c6f85c2 + fc476f1 commit acb814b

2 files changed

Lines changed: 56 additions & 2 deletions

File tree

packages/inquirerer/src/prompt.ts

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import readline from 'readline';
33
import { Readable, Writable } from 'stream';
44

55
import { KEY_CODES, TerminalKeypress } from './keypress';
6-
import { AutocompleteQuestion, CheckboxQuestion, ConfirmQuestion, ListQuestion, NumberQuestion, OptionValue, Question, TextQuestion, Validation, Value } from './question';
6+
import { AutocompleteQuestion, CheckboxQuestion, ConfirmQuestion, ListQuestion, NumberQuestion, OptionValue, PasswordQuestion, Question, TextQuestion, Validation, Value } from './question';
77
import { DefaultResolverRegistry, globalResolverRegistry } from './resolvers';
88
// import { writeFileSync } from 'fs';
99

@@ -788,6 +788,8 @@ export class Inquirerer {
788788
return this.autocomplete(question as AutocompleteQuestion, ctx);
789789
case 'number':
790790
return this.number(question as NumberQuestion, ctx);
791+
case 'password':
792+
return this.password(question as PasswordQuestion, ctx);
791793
case 'text':
792794
return this.text(question as TextQuestion, ctx);
793795
default:
@@ -869,6 +871,53 @@ export class Inquirerer {
869871
});
870872
}
871873

874+
public async password(question: PasswordQuestion, ctx: PromptContext): Promise<string | null> {
875+
if (this.noTty || !this.rl || !this.keypress) {
876+
return null; // Password input requires interactive terminal
877+
}
878+
879+
this.keypress.resume();
880+
const mask = question.mask ?? '*';
881+
let input = '';
882+
883+
// Display the prompt with masked input
884+
const display = (): void => {
885+
this.clearScreen();
886+
const maskedInput = mask.repeat(input.length);
887+
this.displayPrompt(question, ctx, maskedInput);
888+
};
889+
890+
display();
891+
892+
// Handle backspace
893+
this.keypress.on(KEY_CODES.BACKSPACE, () => {
894+
if (input.length > 0) {
895+
input = input.slice(0, -1);
896+
display();
897+
}
898+
});
899+
900+
// Register alphanumeric, symbols, and space keypresses
901+
const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 !@#$%^&*()_+-=[]{}|;:\'",.<>?/`~\\';
902+
chars.split('').forEach(char => {
903+
this.keypress.on(char, () => {
904+
input += char;
905+
display();
906+
});
907+
});
908+
909+
return new Promise<string | null>((resolve) => {
910+
this.keypress.on(KEY_CODES.ENTER, () => {
911+
this.keypress.pause();
912+
if (input.trim() !== '') {
913+
resolve(input);
914+
} else {
915+
resolve(null);
916+
}
917+
});
918+
});
919+
}
920+
872921
public async checkbox(question: CheckboxQuestion, ctx: PromptContext): Promise<OptionValue[]> {
873922
if (this.noTty || !this.rl) {
874923
const options = this.sanitizeOptions(question);

packages/inquirerer/src/question/types.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,5 +71,10 @@ export interface BaseQuestion {
7171
type: 'number';
7272
default?: number;
7373
}
74+
75+
export interface PasswordQuestion extends BaseQuestion {
76+
type: 'password';
77+
mask?: string; // Character to use for masking (default: '*')
78+
}
7479

75-
export type Question = ConfirmQuestion | ListQuestion | AutocompleteQuestion | CheckboxQuestion | TextQuestion | NumberQuestion;
80+
export type Question = ConfirmQuestion | ListQuestion | AutocompleteQuestion | CheckboxQuestion | TextQuestion | NumberQuestion | PasswordQuestion;

0 commit comments

Comments
 (0)