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
JavaScript
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