UNPKG

hardhat

Version:

Hardhat is an extensible developer tool that helps smart contract developers increase productivity by reliably bringing together the tools they want.

134 lines • 5.45 kB
import { createInterface } from "node:readline"; import { styleText } from "node:util"; import { assertHardhatInvariant, HardhatError, } from "@nomicfoundation/hardhat-errors"; import { AsyncMutex } from "@nomicfoundation/hardhat-utils/synchronization"; export class UserInterruptionManagerImplementation { #hooks; #mutex = new AsyncMutex(); constructor(hooks) { this.#hooks = hooks; } async displayMessage(interruptor, message) { return await this.#mutex.exclusiveRun(async () => { return await this.#hooks.runHandlerChain("userInterruptions", "displayMessage", [interruptor, message], defaultDisplayMessage); }); } async requestInput(interruptor, inputDescription) { return await this.#mutex.exclusiveRun(async () => { return await this.#hooks.runHandlerChain("userInterruptions", "requestInput", [interruptor, inputDescription], defaultRequestInput); }); } async requestSecretInput(interruptor, inputDescription) { return await this.#mutex.exclusiveRun(async () => { return await this.#hooks.runHandlerChain("userInterruptions", "requestSecretInput", [interruptor, inputDescription], defaultRequestSecretInput); }); } async uninterrupted(f) { return await this.#mutex.exclusiveRun(f); } } /** * Lines read from stdin that were not the answer to the prompt that read * them. When input is piped, a single chunk usually contains the answers to * several prompts, and readline still emits its remaining lines after the * interface used for the first answer is closed, so they are kept here for * the next prompts. */ const pendingLines = []; function takePendingLine(interruptor, inputDescription) { const line = pendingLines.shift(); if (line !== undefined) { process.stdout.write(styleText("blue", `[${interruptor}]`) + ` ${inputDescription}: `); } return line; } async function defaultDisplayMessage(_context, interruptor, message) { console.log(styleText("blue", `[${interruptor}]`) + ` ${message}`); } async function defaultRequestInput(_context, interruptor, inputDescription) { const pendingLine = takePendingLine(interruptor, inputDescription); if (pendingLine !== undefined) { return pendingLine; } const rl = createInterface({ input: process.stdin, output: process.stdout, terminal: process.stdin.isTTY === true, }); rl.on("line", (line) => pendingLines.push(line)); return await askQuestion(rl, interruptor, inputDescription); } async function defaultRequestSecretInput(_context, interruptor, inputDescription) { const pendingLine = takePendingLine(interruptor, inputDescription); if (pendingLine !== undefined) { return pendingLine; } const rl = createInterface({ input: process.stdin, output: process.stdout, terminal: process.stdin.isTTY === true, }); rl.on("line", (line) => pendingLines.push(line)); /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- We need to access a private property of the readline interface. */ const rlAsAny = rl; let initialMessage; rlAsAny._writeToOutput = (out) => { if (initialMessage === undefined || out.length !== 1) { if (initialMessage === undefined) { initialMessage = out; } assertHardhatInvariant(rlAsAny.output !== undefined, "Expected readline output to be defined"); // We show the initial message as is if (out.startsWith(initialMessage)) { rlAsAny.output.write(initialMessage); out = out.slice(initialMessage.length); } else if (out.trim() === "") { rlAsAny.output.write(out); out = ""; } } // We show the rest of the chars as "*" for (const _ of out) { rlAsAny.output.write("*"); } }; return await askQuestion(rl, interruptor, inputDescription); } /** * Asks the user for input through the given readline interface. * * If the interface closes before an answer arrives (e.g. stdin reaches EOF, or * the user cancels the prompt) it throws instead of never settling. */ async function askQuestion(rl, interruptor, inputDescription) { return await new Promise((resolve, reject) => { let prompted = false; let answered = false; rl.once("close", () => { if (answered) { return; } // The prompt doesn't end with a newline, so we print one to keep the // error off the prompt's line if (prompted) { process.stdout.write("\n"); } reject(new HardhatError(HardhatError.ERRORS.CORE.GENERAL.PROMPT_CLOSED_BEFORE_ANSWER, { interruptor })); }); // If a previous prompt already read stdin's EOF, readline won't receive // another "end" event, so we close the interface ourselves. if (process.stdin.readableEnded) { rl.close(); return; } prompted = true; rl.question(styleText("blue", `[${interruptor}]`) + ` ${inputDescription}: `, (answer) => { answered = true; resolve(answer); rl.close(); }); }); } //# sourceMappingURL=user-interruptions.js.map