UNPKG

hardhat

Version:

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

405 lines (327 loc) 11.5 kB
import type { DebugLogger } from "@nomicfoundation/hardhat-utils/debug"; import { fileURLToPath } from "node:url"; import { HardhatError } from "@nomicfoundation/hardhat-errors"; import { isCi } from "@nomicfoundation/hardhat-utils/ci"; import { createDebug } from "@nomicfoundation/hardhat-utils/debug"; import { ensureError } from "@nomicfoundation/hardhat-utils/error"; import { getRealPath } from "@nomicfoundation/hardhat-utils/fs"; import { findClosestPackageJson, findDependencyPackageJson, readClosestPackageJson, } from "@nomicfoundation/hardhat-utils/package"; import { register } from "tsx/esm/api"; import { isResult } from "../../utils/result.js"; import { BUILTIN_GLOBAL_OPTIONS_DEFINITIONS } from "../builtin-global-options.js"; import { builtinPlugins } from "../builtin-plugins/index.js"; import { importUserConfig, resolveHardhatConfigPath, } from "../config-loading.js"; import { buildGlobalOptionDefinitions } from "../core/global-options.js"; import { resolveProjectRoot } from "../core/hre.js"; import { resolvePluginList } from "../core/plugins/resolve-plugin-list.js"; import { warnAboutUnusedLoadedPlugins } from "../core/plugins/unused-plugins-warning.js"; import { setGlobalHardhatRuntimeEnvironment } from "../global-hre-instance.js"; import { createHardhatRuntimeEnvironment } from "../hre-initialization.js"; import { printErrorMessages } from "./error-handler.js"; import { getGlobalHelpString } from "./help/get-global-help-string.js"; import { getHelpString } from "./help/get-help-string.js"; import { parseBuiltinGlobalOptions, parseGlobalOptions, parseRawArguments, parseTask, parseTaskArguments, } from "./parser.js"; import { sendTaskAnalytics } from "./telemetry/analytics/analytics.js"; import { setupGlobalUnhandledErrorHandlers } from "./telemetry/error-reporter/global-error-handlers.js"; import { sendErrorTelemetry, setCliHardhatConfigPath, } from "./telemetry/error-reporter/reporter.js"; import { printVersionMessage } from "./version.js"; export interface MainOptions { print?: (message: string) => void; registerTsx?: boolean; rethrowErrors?: true; allowNonlocalHardhatInstallation?: true; warnAboutUnusedPlugins?: true; } export async function main( rawArguments: string[], options: MainOptions = {}, ): Promise<void> { // We set up the global unhandled errors before running any functionality setupGlobalUnhandledErrorHandlers(); const print = options.print ?? console.log; const log = createDebug("hardhat:core:cli:main"); let builtinGlobalOptions; let configPath; log("Hardhat CLI started"); try { const cliArguments = parseRawArguments(rawArguments); const usedCliArguments: boolean[] = new Array(cliArguments.length).fill( false, ); builtinGlobalOptions = await parseBuiltinGlobalOptions( cliArguments, usedCliArguments, ); log("Parsed builtin global options"); if (builtinGlobalOptions.version) { return await printVersionMessage(print); } if (builtinGlobalOptions.init) { const { initHardhat, initHardhat3NonInteractive, printTemplatesList } = await import("./init/init.js"); let templateName: string | undefined; let listTemplates = false; for (let i = 0; i < cliArguments.length; i++) { if (usedCliArguments[i]) { continue; } if (cliArguments[i] === "--templates") { usedCliArguments[i] = true; listTemplates = true; } } for (let i = 0; i < cliArguments.length; i++) { if (usedCliArguments[i]) { continue; } if (cliArguments[i] === "--template") { usedCliArguments[i] = true; if (templateName !== undefined) { throw new HardhatError( HardhatError.ERRORS.CORE.ARGUMENTS.DUPLICATED_NAME, { name: "--template" }, ); } if (listTemplates) { throw new HardhatError( HardhatError.ERRORS.CORE.ARGUMENTS.CANNOT_COMBINE_TEMPLATE_AND_TEMPLATES, ); } if ( usedCliArguments[i + 1] === undefined || usedCliArguments[i + 1] === true || cliArguments[i + 1] === undefined || cliArguments[i + 1].startsWith("-") ) { throw new HardhatError( HardhatError.ERRORS.CORE.ARGUMENTS.MISSING_VALUE_FOR_ARGUMENT, { argument: "--template" }, ); } templateName = cliArguments[i + 1]; i++; usedCliArguments[i] = true; } } if (listTemplates) { return await printTemplatesList("hardhat-3", print); } if (templateName !== undefined) { return await initHardhat3NonInteractive({ template: templateName }); } return await initHardhat(); } configPath = await resolveHardhatConfigPath( builtinGlobalOptions.configPath, ); if ( options.allowNonlocalHardhatInstallation !== true && !(await isHardhatInstalledLocallyOrLinked(configPath, log)) ) { throw new HardhatError( HardhatError.ERRORS.CORE.GENERAL.NON_LOCAL_INSTALLATION, ); } setCliHardhatConfigPath(configPath); const projectRoot = await resolveProjectRoot(configPath); const esmErrorPrinted = await printEsmErrorMessageIfNecessary( projectRoot, print, ); if (esmErrorPrinted) { process.exitCode = 1; return; } if (options.registerTsx === true) { register(); } const userConfig = await importUserConfig(configPath); log("User config imported"); const configPlugins = Array.isArray(userConfig.plugins) ? userConfig.plugins : []; const plugins = [...builtinPlugins, ...configPlugins]; const resolvedPlugins = await resolvePluginList(projectRoot, plugins); log("Resolved plugins"); const pluginGlobalOptionDefinitions = buildGlobalOptionDefinitions(resolvedPlugins); const globalOptionDefinitions = new Map([ ...BUILTIN_GLOBAL_OPTIONS_DEFINITIONS, ...pluginGlobalOptionDefinitions, ]); const userProvidedGlobalOptions = await parseGlobalOptions( globalOptionDefinitions, cliArguments, usedCliArguments, ); log("Creating Hardhat Runtime Environment"); const hre = await createHardhatRuntimeEnvironment( userConfig, { ...builtinGlobalOptions, config: configPath, ...userProvidedGlobalOptions, }, projectRoot, { resolvedPlugins, globalOptionDefinitions }, ); if (options.warnAboutUnusedPlugins) { warnAboutUnusedLoadedPlugins(hre.config.plugins); } // This must be the first time we set it, otherwise we let it crash setGlobalHardhatRuntimeEnvironment(hre); const taskOrId = parseTask( cliArguments, usedCliArguments, hre.tasks.rootTasks, ); if (Array.isArray(taskOrId)) { if (taskOrId.length === 0) { const globalHelp = await getGlobalHelpString( hre.tasks.rootTasks, globalOptionDefinitions, ); print(globalHelp); return; } throw new HardhatError( HardhatError.ERRORS.CORE.TASK_DEFINITIONS.TASK_NOT_FOUND, { task: taskOrId.join(" ") }, ); } const task = taskOrId; if (task.isEmpty && usedCliArguments.includes(false)) { const invalidSubtask = cliArguments[usedCliArguments.indexOf(false)]; throw new HardhatError( HardhatError.ERRORS.CORE.TASK_DEFINITIONS.UNRECOGNIZED_SUBTASK, { task: task.id.join(" "), invalidSubtask, }, ); } if (builtinGlobalOptions.help || task.isEmpty) { const taskHelp = await getHelpString(task, globalOptionDefinitions); print(taskHelp); return; } const taskArguments = parseTaskArguments( cliArguments, usedCliArguments, task, ); log(`Running task "${task.id.join(" ")}"`); const [taskResult] = await Promise.all([ task.run(taskArguments), sendTaskAnalytics(task.id, "hardhat"), ]); if (isResult(taskResult) && !taskResult.success) { process.exitCode = 1; } if (!isCi() && process.stdout.isTTY === true) { try { const { BannerManager } = await import("./banner-manager.js"); const bannerManager = await BannerManager.getInstance(); await bannerManager.showBanner(200); } catch (bannerError) { log("Error showing banner", bannerError); } } const deprecatedConnectWasCalled = Boolean( "wasConnectCalled" in hre.network && hre.network.wasConnectCalled !== undefined && typeof hre.network.wasConnectCalled === "function" && hre.network.wasConnectCalled(), ); if (deprecatedConnectWasCalled) { console.warn( "WARNING: hre.network.connect() is deprecated and will be removed in a future version. " + "Use hre.network.create() or hre.network.getOrCreate() instead.", ); } } catch (error) { ensureError(error); await printErrorMessages(error, builtinGlobalOptions?.showStackTraces); try { await sendErrorTelemetry(error); } catch (e) { log("Couldn't report error to sentry: %O", e); } if (options.rethrowErrors) { throw error; } process.exitCode = 1; } } /** * Returns true if Hardhat is installed locally or linked from its repository, * by looking for it using the node module resolution logic. * * If a config file is provided, we start looking for it from there. Otherwise, * we use the current working directory. */ async function isHardhatInstalledLocallyOrLinked( configPath: string, log: DebugLogger, ): Promise<boolean> { try { // Based on Node.js resolution algorithm find the real path // of the project's version of Hardhat const realPathToResolvedPackageJson = await findDependencyPackageJson( configPath ?? process.cwd(), "hardhat", ); // Find the executing code's Hardhat Package.json const thisPackageJson = await findClosestPackageJson( fileURLToPath(import.meta.url), ); // We need to get the realpaths here, as hardhat may be linked and // running with `node --preserve-symlinks` const isLocalOrLinked = realPathToResolvedPackageJson === (await getRealPath(thisPackageJson)); if (!isLocalOrLinked) { log("Determined that Hardhat is not installed locally/linked"); log(` resolved package.json: ${realPathToResolvedPackageJson}`); log(` current package.json: ${thisPackageJson}`); } return isLocalOrLinked; } catch (error) { log("Error during installed locally/linked test", error); return false; } } /** * Prints an error message if the user is running Hardhat on CJS mode, returning * `true` if the message was printed. */ async function printEsmErrorMessageIfNecessary( projectRoot: string, print: (message: string) => void, ): Promise<boolean> { const packageJson = await readClosestPackageJson(projectRoot); if (packageJson.type !== "module") { print(`Hardhat only supports ESM projects. Please make sure you have \`"type": "module"\` in your package.json. You can set it automatically by running: npm pkg set type="module" `); return true; } return false; }