UNPKG

hardhat

Version:

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

357 lines (299 loc) 10.5 kB
import type StackTraceParserT from "stacktrace-parser"; import chalk from "chalk"; import debug from "debug"; import fsExtra from "fs-extra"; import path from "path"; import semver from "semver"; import { HardhatArguments, HardhatConfig, HardhatUserConfig, SolcConfig, } from "../../../types"; import { HardhatContext } from "../../context"; import { findClosestPackageJson } from "../../util/packageInfo"; import { HardhatError } from "../errors"; import { ERRORS } from "../errors-list"; import { getUserConfigPath } from "../project-structure"; import { SUPPORTED_SOLIDITY_VERSION_RANGE } from "../../hardhat-network/stack-traces/constants"; import { resolveConfig } from "./config-resolution"; import { DEFAULT_SOLC_VERSION } from "./default-config"; const log = debug("hardhat:core:config"); export function importCsjOrEsModule(filePath: string): any { try { const imported = require(filePath); return imported.default !== undefined ? imported.default : imported; } catch (e: any) { if (e.code === "ERR_REQUIRE_ESM") { throw new HardhatError( ERRORS.GENERAL.ESM_PROJECT_WITHOUT_CJS_CONFIG, {}, e ); } // eslint-disable-next-line @nomicfoundation/hardhat-internal-rules/only-hardhat-error throw e; } } export function resolveConfigPath(configPath: string | undefined) { if (configPath === undefined) { configPath = getUserConfigPath(); } else { if (!path.isAbsolute(configPath)) { configPath = path.join(process.cwd(), configPath); configPath = path.normalize(configPath); } } return configPath; } export function loadConfigAndTasks( hardhatArguments?: Partial<HardhatArguments>, { showEmptyConfigWarning = false, showSolidityConfigWarnings = false, }: { showEmptyConfigWarning?: boolean; showSolidityConfigWarnings?: boolean; } = { showEmptyConfigWarning: false, showSolidityConfigWarnings: false, } ): { resolvedConfig: HardhatConfig; userConfig: HardhatUserConfig } { const { validateConfig, validateResolvedConfig } = require("./config-validation") as typeof import("./config-validation"); let configPath = hardhatArguments !== undefined ? hardhatArguments.config : undefined; configPath = resolveConfigPath(configPath); log(`Loading Hardhat config from ${configPath}`); // Before loading the builtin tasks, the default and user's config we expose // the config env in the global object. const configEnv = require("./config-env"); const globalAsAny: any = global; Object.entries(configEnv).forEach( ([key, value]) => (globalAsAny[key] = value) ); const ctx = HardhatContext.getHardhatContext(); ctx.setConfigLoadingAsStarted(); let userConfig; try { require("../tasks/builtin-tasks"); userConfig = importCsjOrEsModule(configPath); } catch (e) { analyzeModuleNotFoundError(e, configPath); // eslint-disable-next-line @nomicfoundation/hardhat-internal-rules/only-hardhat-error throw e; } finally { ctx.setConfigLoadingAsFinished(); } if (showEmptyConfigWarning) { checkEmptyConfig(userConfig, { showSolidityConfigWarnings }); } validateConfig(userConfig); if (showSolidityConfigWarnings) { checkMissingSolidityConfig(userConfig); } // To avoid bad practices we remove the previously exported stuff Object.keys(configEnv).forEach((key) => (globalAsAny[key] = undefined)); const frozenUserConfig = deepFreezeUserConfig(userConfig); const resolved = resolveConfig(configPath, userConfig); for (const extender of HardhatContext.getHardhatContext().configExtenders) { extender(resolved, frozenUserConfig); } validateResolvedConfig(resolved); if (showSolidityConfigWarnings) { checkUnsupportedSolidityConfig(resolved); checkUnsupportedRemappings(resolved); } return { resolvedConfig: resolved, userConfig: frozenUserConfig }; } function deepFreezeUserConfig( config: any, propertyPath: Array<string | number | symbol> = [] ) { if (typeof config !== "object" || config === null) { return config; } return new Proxy(config, { get(target: any, property: string | number | symbol, receiver: any): any { return deepFreezeUserConfig(Reflect.get(target, property, receiver), [ ...propertyPath, property, ]); }, set( target: any, property: string | number | symbol, _value: any, _receiver: any ): boolean { throw new HardhatError(ERRORS.GENERAL.USER_CONFIG_MODIFIED, { path: [...propertyPath, property] .map((pathPart) => pathPart.toString()) .join("."), }); }, }); } /** * Receives an Error and checks if it's a MODULE_NOT_FOUND and the reason that * caused it. * * If it can infer the reason, it throws an appropiate error. Otherwise it does * nothing. */ export function analyzeModuleNotFoundError(error: any, configPath: string) { const stackTraceParser = require("stacktrace-parser") as typeof StackTraceParserT; if (error.code !== "MODULE_NOT_FOUND") { return; } const stackTrace = stackTraceParser.parse(error.stack); const throwingFile = stackTrace .filter((x) => x.file !== null) .map((x) => x.file!) // ignore frames related to source map support .filter((x) => !x.includes(path.join("@cspotcode", "source-map-support"))) .find((x) => path.isAbsolute(x)); if (throwingFile === null || throwingFile === undefined) { return; } // if the error comes from the config file, we ignore it because we know it's // a direct import that's missing if (throwingFile === configPath) { return; } const packageJsonPath = findClosestPackageJson(throwingFile); if (packageJsonPath === null) { return; } const packageJson = fsExtra.readJsonSync(packageJsonPath); const peerDependencies: { [name: string]: string } = packageJson.peerDependencies ?? {}; if (peerDependencies["@nomiclabs/buidler"] !== undefined) { throw new HardhatError(ERRORS.PLUGINS.BUIDLER_PLUGIN, { plugin: packageJson.name, }); } // if the problem doesn't come from a hardhat plugin, we ignore it if (peerDependencies.hardhat === undefined) { return; } const missingPeerDependencies: { [name: string]: string } = {}; for (const [peerDependency, version] of Object.entries(peerDependencies)) { const peerDependencyPackageJson = readPackageJson( peerDependency, configPath ); if (peerDependencyPackageJson === undefined) { missingPeerDependencies[peerDependency] = version; } } const missingPeerDependenciesNames = Object.keys(missingPeerDependencies); if (missingPeerDependenciesNames.length > 0) { throw new HardhatError(ERRORS.PLUGINS.MISSING_DEPENDENCIES, { plugin: packageJson.name, missingDependencies: missingPeerDependenciesNames.join(", "), missingDependenciesVersions: Object.entries(missingPeerDependencies) .map(([name, version]) => `"${name}@${version}"`) .join(" "), }); } } interface PackageJson { name: string; version: string; peerDependencies?: { [name: string]: string; }; } function readPackageJson( packageName: string, configPath: string ): PackageJson | undefined { const resolve = require("resolve") as typeof import("resolve"); try { const packageJsonPath = resolve.sync( path.join(packageName, "package.json"), { basedir: path.dirname(configPath), } ); return require(packageJsonPath); } catch { return undefined; } } function checkEmptyConfig( userConfig: any, { showSolidityConfigWarnings }: { showSolidityConfigWarnings: boolean } ) { if (userConfig === undefined || Object.keys(userConfig).length === 0) { let warning = `Hardhat config is returning an empty config object, check the export from the config file if this is unexpected.\n`; // This 'learn more' section is also printed by the solidity config warning, // so we need to check to avoid printing it twice if (!showSolidityConfigWarnings) { warning += `\nLearn more about configuring Hardhat at https://hardhat.org/config\n`; } console.warn(chalk.yellow(warning)); } } function checkMissingSolidityConfig(userConfig: any) { if (userConfig.solidity === undefined) { console.warn( chalk.yellow( `Solidity compiler is not configured. Version ${DEFAULT_SOLC_VERSION} will be used by default. Add a 'solidity' entry to your configuration to suppress this warning. Learn more about compiler configuration at https://hardhat.org/config ` ) ); } } function checkUnsupportedSolidityConfig(resolvedConfig: HardhatConfig) { const configuredCompilers = getConfiguredCompilers(resolvedConfig.solidity); const solcVersions = configuredCompilers.map((x) => x.version); const unsupportedVersions: string[] = []; for (const solcVersion of solcVersions) { if ( !semver.satisfies(solcVersion, SUPPORTED_SOLIDITY_VERSION_RANGE) && !unsupportedVersions.includes(solcVersion) ) { unsupportedVersions.push(solcVersion); } } if (unsupportedVersions.length > 0) { console.warn( chalk.yellow( `Solidity ${unsupportedVersions.join(", ")} ${ unsupportedVersions.length === 1 ? "is" : "are" } not fully supported yet. You can still use Hardhat, but some features, like stack traces, might not work correctly. Learn more at https://hardhat.org/hardhat-runner/docs/reference/solidity-support ` ) ); } } function checkUnsupportedRemappings({ solidity }: HardhatConfig) { const solcConfigs = [ ...solidity.compilers, ...Object.values(solidity.overrides), ]; const remappings = solcConfigs.filter( ({ settings }) => settings.remappings !== undefined ); if (remappings.length > 0) { console.warn( chalk.yellow( `Solidity remappings are not currently supported; you may experience unexpected compilation results. Remove any 'remappings' fields from your configuration to suppress this warning. Learn more about compiler configuration at https://hardhat.org/config ` ) ); } } export function getConfiguredCompilers( solidityConfig: HardhatConfig["solidity"] ): SolcConfig[] { const compilerVersions = solidityConfig.compilers; const overrideVersions = Object.values(solidityConfig.overrides); return [...compilerVersions, ...overrideVersions]; }