Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add a built-in console task #5592

Merged
merged 13 commits into from
Aug 14, 2024
Merged
28 changes: 28 additions & 0 deletions v-next/hardhat/src/internal/builtin-plugins/console/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import type { HardhatPlugin } from "@ignored/hardhat-vnext-core/types/plugins";

import { task } from "@ignored/hardhat-vnext-core/config";

const hardhatPlugin: HardhatPlugin = {
id: "console",
tasks: [
task("console", "Opens a hardhat console")
.setAction(import.meta.resolve("./task-action.js"))
.addOption({
name: "history",
description: "Path to a history file",
defaultValue: "console-history.txt",
})
.addFlag({
name: "noCompile",
description: "Don't compile before running this task",
})
.addVariadicArgument({
name: "commands",
description: "Commands to run in the console",
defaultValue: [".help"],
})
.build(),
],
};

export default hardhatPlugin;
70 changes: 70 additions & 0 deletions v-next/hardhat/src/internal/builtin-plugins/console/task-action.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import type { NewTaskActionFunction } from "@ignored/hardhat-vnext-core/types/tasks";
import type { REPLServer } from "node:repl";

import path from "node:path";
import repl from "node:repl";

import { getCacheDir } from "@ignored/hardhat-vnext-core/global-dir";
import debug from "debug";

const log = debug("hardhat:core:tasks:console");

interface ConsoleActionArguments {
commands: string[];
history: string;
noCompile: boolean;
// We accept ReplOptions as an argument to allow tests overriding the IO streams
options?: repl.ReplOptions;
Comment on lines +16 to +17
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is really interesting! I never thought about it.

}

const consoleAction: NewTaskActionFunction<ConsoleActionArguments> = async (
{ commands, history, noCompile, options },
hre,
) => {
// Resolve the history path if it is not empty
let historyPath: string | undefined;
if (history !== "") {
const globalCacheDir = await getCacheDir();
historyPath = path.isAbsolute(history)
? history
: path.resolve(globalCacheDir, history);
}

// If noCompile is false, run the compile task first
if (!noCompile) {
// todo: run compile task
}

return new Promise<REPLServer>(async (resolve) => {
// Start a new REPL server with the default options
const replServer = repl.start(options);

// Resolve the task action promise only when the REPL server exits
replServer.on("exit", () => {
resolve(replServer);
});

// Add the Hardhat Runtime Environment to the REPL context
replServer.context.hre = hre;

// Set up the REPL history file if the historyPath has been set
if (historyPath !== undefined) {
await new Promise<void>((resolveSetupHistory) => {
replServer.setupHistory(historyPath, (err: Error | null) => {
// Fail silently if the history file cannot be set up
if (err !== null) {
log("Failed to setup REPL history", err);
}
resolveSetupHistory();
});
});
}

// Execute each command in the REPL server
for (const command of commands) {
replServer.write(`${command}\n`);
}
});
};

export default consoleAction;
9 changes: 8 additions & 1 deletion v-next/hardhat/src/internal/builtin-plugins/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
import type { HardhatPlugin } from "@ignored/hardhat-vnext-core/types/plugins";

import clean from "./clean/index.js";
import console from "./console/index.js";
import hardhatFoo from "./hardhat-foo/index.js";
import run from "./run/index.js";

// Note: When importing a plugin, you have to export its types, so that its
// type extensions, if any, also get loaded.
export type * from "./clean/index.js";
export type * from "./console/index.js";
export type * from "./hardhat-foo/index.js";
export type * from "./run/index.js";

export const builtinPlugins: HardhatPlugin[] = [clean, hardhatFoo, run];
export const builtinPlugins: HardhatPlugin[] = [
clean,
console,
hardhatFoo,
run,
];
182 changes: 182 additions & 0 deletions v-next/hardhat/test/internal/builtin-plugins/console/task-action.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
import type { HardhatRuntimeEnvironment } from "@ignored/hardhat-vnext-core/types/hre";
import type repl from "node:repl";

import assert from "node:assert/strict";
import fsPromises from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { PassThrough } from "node:stream";
import { afterEach, before, beforeEach, describe, it } from "node:test";

import { ensureError } from "@ignored/hardhat-vnext-utils/error";
import { exists, remove } from "@ignored/hardhat-vnext-utils/fs";
import debug from "debug";

import { createHardhatRuntimeEnvironment } from "../../../../src/hre.js";
import consoleAction from "../../../../src/internal/builtin-plugins/console/task-action.js";
import { useFixtureProject } from "../../../helpers/project.js";

const log = debug("hardhat:test:console:task-action");

describe("console/task-action", function () {
let hre: HardhatRuntimeEnvironment;
let options: repl.ReplOptions;

before(async function () {
hre = await createHardhatRuntimeEnvironment({});
});

beforeEach(function () {
const input = new PassThrough();
const output = new PassThrough();
output.pipe(process.stdout);
options = {
input,
output,
};
});

describe("javascript", function () {
useFixtureProject("run-js-script");

it("should throw inside the console if script does not exist", async function () {
const replServer = await consoleAction(
{
commands: ['await import("./scripts/non-existent.js");', ".exit"],
history: "",
noCompile: false,
options,
},
hre,
);
ensureError(replServer.lastError);
});

it("should run a script inside the console successfully", async function () {
const replServer = await consoleAction(
{
commands: [".help", 'await import("./scripts/success.js");', ".exit"],
history: "",
noCompile: false,
options,
},
hre,
);
assert.equal(replServer.lastError, undefined);
});

it("should throw inside the console if the script throws", async function () {
const replServer = await consoleAction(
{
commands: ['await import("./scripts/throws.js");', ".exit"],
history: "",
noCompile: false,
options,
},
hre,
);
ensureError(replServer.lastError);
});
});

describe("typescript", function () {
useFixtureProject("run-ts-script");

it("should throw inside the console if script does not exist", async function () {
const replServer = await consoleAction(
{
commands: ['await import("./scripts/non-existent.ts");', ".exit"],
history: "",
noCompile: false,
options,
},
hre,
);
ensureError(replServer.lastError);
});

it("should run a script inside the console successfully", async function () {
const replServer = await consoleAction(
{
commands: ['await import("./scripts/success.ts");', ".exit"],
history: "",
noCompile: false,
options,
},
hre,
);
assert.equal(replServer.lastError, undefined);
});

it("should throw inside the console if the script throws", async function () {
const replServer = await consoleAction(
{
commands: ['await import("./scripts/throws.ts");', ".exit"],
history: "",
noCompile: false,
options,
},
hre,
);
ensureError(replServer.lastError);
});
});

describe("context", function () {
it("should expose the Hardhat Runtime Environment", async function () {
const replServer = await consoleAction(
{
commands: ["console.log(hre);", ".exit"],
history: "",
noCompile: false,
options,
},
hre,
);
assert.equal(replServer.lastError, undefined);
});
});

describe("history", function () {
let cacheDir: string;
let history: string;

beforeEach(async function () {
cacheDir = await fsPromises.mkdtemp(
path.resolve(os.tmpdir(), "console-action-test-"),
);
history = path.resolve(cacheDir, "console-history.txt");
});

afterEach(async function () {
try {
await remove(cacheDir);
} catch (error) {
log("Failed to remove temporary cache dir", error);
}
});

it("should create a history file", async function () {
let historyExists = await exists(history);
assert.ok(
!historyExists,
"History file exists before running the console",
);
const replServer = await consoleAction(
{
commands: [".help", ".exit"],
history,
noCompile: false,
options,
},
hre,
);
assert.equal(replServer.lastError, undefined);
historyExists = await exists(history);
assert.ok(
historyExists,
"History file does not exist after running the console",
);
});
});
});
1 change: 1 addition & 0 deletions v-next/hardhat/test/internal/cli/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ Usage: hardhat [GLOBAL OPTIONS] <TASK> [SUBTASK] [TASK OPTIONS] [--] [TASK ARGUM
AVAILABLE TASKS:

clean Clears the cache and deletes all artifacts
console Opens a hardhat console
example Example task
run Runs a user-defined script after compiling the project
task A task that uses arg1
Expand Down