mirror of
https://github.com/denoland/std.git
synced 2024-11-22 04:59:05 +00:00
101 lines
3.0 KiB
TypeScript
101 lines
3.0 KiB
TypeScript
// Copyright 2018-2024 the Deno authors. All rights reserved. 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
|
|
|
|
// 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 no-eval
|
|
* 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;
|
|
}
|
|
|
|
// Make the output consistent with the built-in prompt()
|
|
message += " ";
|
|
const callback = !mask ? undefined : (n: number) => {
|
|
output.writeSync(CLR);
|
|
output.writeSync(encoder.encode(`${message}${mask.repeat(n)}`));
|
|
};
|
|
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));
|
|
}
|