hardhat
Version:
Hardhat is an extensible developer tool that helps smart contract developers increase productivity by reliably bringing together the tools they want.
150 lines • 6.87 kB
JavaScript
import { HardhatError, assertHardhatInvariant, } from "@nomicfoundation/hardhat-errors";
import { ensureError } from "@nomicfoundation/hardhat-utils/error";
import { detectPluginNpmDependencyProblems } from "../plugins/detect-plugin-npm-dependency-problems.js";
import { formatTaskId } from "./utils.js";
import { validateTaskArgumentValue } from "./validations.js";
export class ResolvedTask {
id;
description;
actions;
options;
positionalArguments;
pluginId;
subtasks;
#hre;
static createEmptyTask(hre, id, description, pluginId) {
return new ResolvedTask(id, description, [{ pluginId, action: undefined }], new Map(), [], pluginId, new Map(), hre);
}
static createNewTask(hre, id, description, action, options, positionalArguments, pluginId) {
return new ResolvedTask(id, description, [{ pluginId, action }], new Map(Object.entries(options)), positionalArguments, pluginId, new Map(), hre);
}
constructor(id, description, actions, options, positionalArguments, pluginId, subtasks, hre) {
this.id = id;
this.description = description;
this.actions = actions;
this.options = options;
this.positionalArguments = positionalArguments;
this.pluginId = pluginId;
this.subtasks = subtasks;
this.#hre = hre;
}
get isEmpty() {
return this.actions.length === 1 && this.actions[0].action === undefined;
}
/**
* This method runs the task with the given arguments.
* It validates the arguments, resolves the file actions, and runs the task
* actions by calling them in order.
*
* @param taskArguments The arguments to run the task with.
* @returns The result of running the task.
* @throws HardhatError if the task is empty, a required argument is missing,
* a argument has an invalid type, or the file actions can't be resolved.
*/
async run(taskArguments = {}) {
if (this.isEmpty) {
throw new HardhatError(HardhatError.ERRORS.CORE.TASK_DEFINITIONS.EMPTY_TASK, {
task: formatTaskId(this.id),
});
}
const providedArgumentNames = new Set(Object.keys(taskArguments));
const argumentDefinitions = [
...this.options.values(),
...this.positionalArguments,
];
const validatedTaskArguments = {};
for (const argumentDefinition of argumentDefinitions) {
const value = taskArguments[argumentDefinition.name];
const isPositional = "isVariadic" in argumentDefinition;
if (isPositional) {
this.#validateRequiredArgument(argumentDefinition, value);
}
if (value !== undefined) {
validateTaskArgumentValue(argumentDefinition.name, argumentDefinition.type, value, isPositional && argumentDefinition.isVariadic, this.id);
}
// resolve defaults for optional arguments
validatedTaskArguments[argumentDefinition.name] =
value ?? argumentDefinition.defaultValue;
providedArgumentNames.delete(argumentDefinition.name);
}
// At this point, the set should be empty as all the task arguments have
// been processed. If there are any extra arguments, an error is thrown
this.#validateExtraArguments(providedArgumentNames);
const next = async (nextTaskArguments, currentIndex = this.actions.length - 1) => {
// The first action may be empty if the task was originally an empty task
const currentAction = this.actions[currentIndex].action ??
(async () => ({
default: () => { },
}));
const actionFn = await this.#resolveImportAction(currentAction, this.actions[currentIndex].pluginId);
if (currentIndex === 0) {
/* eslint-disable-next-line @typescript-eslint/consistent-type-assertions --
We know that the first action in the array is a NewTaskActionFunction */
return actionFn(nextTaskArguments, this.#hre);
}
return actionFn(nextTaskArguments, this.#hre, async (newTaskArguments) => {
return next(newTaskArguments, currentIndex - 1);
});
};
return next(validatedTaskArguments);
}
/**
* Validates that a required argument has a value. A argument is required if
* it doesn't have a default value.
*
* @throws HardhatError if the argument is required and doesn't have a value.
*/
#validateRequiredArgument(argument, value) {
if (argument.defaultValue === undefined && value === undefined) {
throw new HardhatError(HardhatError.ERRORS.CORE.TASK_DEFINITIONS.MISSING_VALUE_FOR_TASK_ARGUMENT, {
argument: argument.name,
task: formatTaskId(this.id),
});
}
}
/**
* Validates that no extra arguments were provided in the task arguments.
*
* @throws HardhatError if extra arguments were provided. The error message
* includes the name of the first extra argument.
*/
#validateExtraArguments(providedArgumentNames) {
if (providedArgumentNames.size > 0) {
throw new HardhatError(HardhatError.ERRORS.CORE.TASK_DEFINITIONS.UNRECOGNIZED_TASK_OPTION, {
option: [...providedArgumentNames][0],
task: formatTaskId(this.id),
});
}
}
/**
* Resolves the action file for a task. The action file is imported and the
* default export function is returned.
*
* @throws HardhatError if the module can't be imported or doesn't have a
* default export function.
*/
async #resolveImportAction(action, actionPluginId) {
let resolvedActionFn;
try {
resolvedActionFn = await action();
}
catch (error) {
ensureError(error);
if (actionPluginId !== undefined) {
const plugin = this.#hre.config.plugins.find((p) => p.id === actionPluginId);
assertHardhatInvariant(plugin !== undefined, `Plugin with id ${actionPluginId} not found.`);
await detectPluginNpmDependencyProblems(this.#hre.config.paths.root, plugin, error);
}
throw new HardhatError(HardhatError.ERRORS.CORE.TASK_DEFINITIONS.INVALID_ACTION_IMPORT, {
task: formatTaskId(this.id),
}, error);
}
if (typeof resolvedActionFn.default !== "function") {
throw new HardhatError(HardhatError.ERRORS.CORE.TASK_DEFINITIONS.INVALID_ACTION, {
task: formatTaskId(this.id),
});
}
return resolvedActionFn.default;
}
}
//# sourceMappingURL=resolved-task.js.map