-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #5592 from NomicFoundation/galargh/console
feat: add a built-in console task
- Loading branch information
Showing
6 changed files
with
306 additions
and
2 deletions.
There are no files selected for viewing
28 changes: 28 additions & 0 deletions
28
v-next/hardhat/src/internal/builtin-plugins/console/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
76 changes: 76 additions & 0 deletions
76
v-next/hardhat/src/internal/builtin-plugins/console/task-action.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
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; | ||
} | ||
|
||
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 !== "") { | ||
// TODO(#5599): Replace with hre.config.paths.cache once it is available | ||
const cacheDir = await getCacheDir(); | ||
historyPath = path.isAbsolute(history) | ||
? history | ||
: path.resolve(cacheDir, history); | ||
} | ||
|
||
// If noCompile is false, run the compile task first | ||
if (!noCompile) { | ||
// TODO(#5600): 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; | ||
replServer.context.config = hre.config; | ||
replServer.context.tasks = hre.tasks; | ||
replServer.context.globalOptions = hre.globalOptions; | ||
replServer.context.hooks = hre.hooks; | ||
replServer.context.interruptions = hre.interruptions; | ||
|
||
// 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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
]; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
192 changes: 192 additions & 0 deletions
192
v-next/hardhat/test/internal/builtin-plugins/console/task-action.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,192 @@ | ||
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 () { | ||
// Using process.stdin for the input during tests is not reliable as it | ||
// causes the test runner to hang indefinitely. We use a PassThrough stream | ||
// instead. This, in turn, prevents us from using process.stdout for output. | ||
// Hence, we use a PassThrough stream for output as well. | ||
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 () { | ||
// TODO(#5601): Use the mkdtemp from hardhat-utils once it's available | ||
// We use a temporary cache dir to avoid conflicts with other tests | ||
// and global user settings. | ||
cacheDir = await fsPromises.mkdtemp( | ||
path.resolve(os.tmpdir(), "console-action-test-"), | ||
); | ||
history = path.resolve(cacheDir, "console-history.txt"); | ||
}); | ||
|
||
afterEach(async function () { | ||
// We try to remove the temporary cache dir after each test, but we don't | ||
// fail the test if it fails. For example, we have observed that in GHA | ||
// on Windows, the temp dir cannot be removed due to permission issues. | ||
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", | ||
); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters