forked from denoland/std
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathprompt_secret.ts
130 lines (116 loc) · 3.93 KB
/
prompt_secret.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
// Copyright 2018-2025 the Deno authors. MIT license.
const input = Deno.stdin;
const output = Deno.stdout;
const encoder = new TextEncoder();
const decoder = new TextDecoder();
const LF = "\n".charCodeAt(0); // ^J - Enter on Linux
const CR = "\r".charCodeAt(0); // ^M - Enter on macOS and Windows (CRLF)
const BS = "\b".charCodeAt(0); // ^H - Backspace on Linux and Windows
const DEL = 0x7f; // ^? - Backspace on macOS
const CLR = encoder.encode("\r\u001b[K"); // Clear the current line
const MOVE_LINE_UP = encoder.encode("\r\u001b[1F"); // Move to previous line
// The `cbreak` option is not supported on Windows
const setRawOptions = Deno.build.os === "windows"
? undefined
: { cbreak: true };
/** Options for {@linkcode promptSecret}. */
export type PromptSecretOptions = {
/** A character to print instead of the user's input. */
mask?: string;
/** Clear the current line after the user's input. */
clear?: boolean;
};
/**
* Shows the given message and waits for the user's input. Returns the user's input as string.
* This is similar to `prompt()` but it print user's input as `*` to prevent password from being shown.
* Use an empty `mask` if you don't want to show any character.
*
* @param message The prompt message to show to the user.
* @param options The options for the prompt.
* @returns The string that was entered or `null` if stdin is not a TTY.
*
* @example Usage
* ```ts ignore
* import { promptSecret } from "@std/cli/prompt-secret";
*
* const password = promptSecret("Please provide the password:");
* if (password !== "some-password") {
* throw new Error("Access denied");
* }
* ```
*/
export function promptSecret(
message = "Secret",
options?: PromptSecretOptions,
): string | null {
const { mask = "*", clear } = options ?? {};
if (!input.isTerminal()) {
return null;
}
const { columns } = Deno.consoleSize();
let previousLength = 0;
// Make the output consistent with the built-in prompt()
message += " ";
const callback = !mask ? undefined : (n: number) => {
let line = `${message}${mask.repeat(n)}`;
const currentLength = line.length;
const charsPastLineLength = line.length % columns;
if (line.length > columns) {
line = line.slice(
-1 * (charsPastLineLength === 0 ? columns : charsPastLineLength),
);
}
// If the user has deleted a character
if (currentLength < previousLength) {
// Then clear the current line.
output.writeSync(CLR);
if (charsPastLineLength === 0) {
// And if there's no characters on the current line, return to previous line.
output.writeSync(MOVE_LINE_UP);
}
} else {
// Always jump the cursor back to the beginning of the line unless it's the first character.
if (charsPastLineLength !== 1) {
output.writeSync(CLR);
}
}
output.writeSync(encoder.encode(line));
previousLength = currentLength;
};
output.writeSync(encoder.encode(message));
Deno.stdin.setRaw(true, setRawOptions);
try {
return readLineFromStdinSync(callback);
} finally {
if (clear) {
output.writeSync(CLR);
} else {
output.writeSync(encoder.encode("\n"));
}
Deno.stdin.setRaw(false);
}
}
// Slightly modified from Deno's runtime/js/41_prompt.js
// This implementation immediately break on CR or LF and accept callback.
// The original version waits LF when CR is received.
// https://github.com/denoland/deno/blob/e4593873a9c791238685dfbb45e64b4485884174/runtime/js/41_prompt.js#L52-L77
function readLineFromStdinSync(callback?: (n: number) => void): string {
const c = new Uint8Array(1);
const buf = [];
while (true) {
const n = input.readSync(c);
if (n === null || n === 0) {
break;
}
if (c[0] === CR || c[0] === LF) {
break;
}
if (c[0] === BS || c[0] === DEL) {
buf.pop();
} else {
buf.push(c[0]!);
}
if (callback) callback(buf.length);
}
return decoder.decode(new Uint8Array(buf));
}