hardhat
Version:
Hardhat is an extensible developer tool that helps smart contract developers increase productivity by reliably bringing together the tools they want.
145 lines • 5.98 kB
JavaScript
import path from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
import { HardhatError } from "@nomicfoundation/hardhat-errors";
import { ensureError } from "@nomicfoundation/hardhat-utils/error";
import { exists, findUp, getRealPath } from "@nomicfoundation/hardhat-utils/fs";
import { isObject } from "@nomicfoundation/hardhat-utils/lang";
import { resolveFromRoot } from "@nomicfoundation/hardhat-utils/path";
import debug from "debug";
const log = debug("hardhat:core:config-loading");
/**
* This cache stores any `.ts` files compiled using `tsImport`.
* Since this method does not cache compiled files by default, we implement our own caching mechanism.
*/
const compiledConfigFile = new Map();
/**
* Resolves the path to the Hardhat config file using these rules:
* - If the user provided a path, that path is returned.
* - Otherwise, if the HARDHAT_CONFIG env var is set, that path is returned.
* - Otherwise, the closest Hardhat config file to the current working
* directory is returned.
*
* @param userProvidedPath An optional path to the Hardhat config file, provided
* by the user.
* @returns The path to the Hardhat config file, as an absolute path.
* @throws HardhatError If no Hardhat config file can be found.
*/
export async function resolveHardhatConfigPath(userProvidedPath) {
if (userProvidedPath !== undefined) {
return normalizeConfigPath(userProvidedPath);
}
if (process.env.HARDHAT_CONFIG !== undefined) {
log("Using config file path from the HARDHAT_CONFIG env var");
return normalizeConfigPath(process.env.HARDHAT_CONFIG);
}
return findClosestHardhatConfig();
}
/**
* Finds the closest Hardhat config file to the current working directory.
*
* @returns The absolute path to the closest Hardhat config file.
* @throw HardhatError if no Hardhat config file can be found.
*/
export async function findClosestHardhatConfig(from) {
let hardhatConfigPath = await findUp("hardhat.config.ts", from);
if (hardhatConfigPath !== undefined) {
return hardhatConfigPath;
}
hardhatConfigPath = await findUp("hardhat.config.js", from);
if (hardhatConfigPath !== undefined) {
return hardhatConfigPath;
}
throw new HardhatError(HardhatError.ERRORS.CORE.GENERAL.NO_CONFIG_FILE_FOUND);
}
/**
* Imports the user config and returns it.
* @param configPath The path to the config file.
* @returns The user config.
*/
export async function importUserConfig(configPath) {
const normalizedPath = await normalizeConfigPath(configPath);
const imported = await importConfigFileWithTsxFallback(pathToFileURL(normalizedPath).href);
if (!("default" in imported)) {
throw new HardhatError(HardhatError.ERRORS.CORE.GENERAL.NO_CONFIG_EXPORTED, {
configPath,
});
}
const config = imported.default;
if (!isObject(config)) {
throw new HardhatError(HardhatError.ERRORS.CORE.GENERAL.INVALID_CONFIG_OBJECT, {
configPath,
});
}
return config;
}
/**
* Returns an absolute version of the config path, throwing if the path
* doesn't exist.
*
* @param configPath The path to the config file.
* @returns The absolute path to the config file.
* @throws HardhatError if the path doesn't exist.
*/
async function normalizeConfigPath(configPath) {
const normalizedPath = resolveFromRoot(process.cwd(), configPath);
if (!(await exists(normalizedPath))) {
throw new HardhatError(HardhatError.ERRORS.CORE.GENERAL.INVALID_CONFIG_PATH, {
configPath,
});
}
return normalizedPath;
}
/**
* Handles the runtime import of ".ts" files. This is necessary in situations such as plain
* Node.js, where files are expected to be compiled before execution, or when using tools
* like "vitest", which support TypeScript only when importing from local/user files (i.e.
* not from the "node_modules" folder).
*
* If a ".ts" file is loaded at runtime without prior compilation, it will throw an error.
* This function compiles any ".ts" file on the fly to prevent such issues.
*
* This function uses `tsx`'s `tsImport`, which doesn't cache the compiled files, so we
* implement our own caching mechanism.
*/
async function importConfigFileWithTsxFallback(configPath) {
try {
try {
return await import(configPath);
}
catch (error) {
ensureError(error);
if ("code" in error &&
error.code === "ERR_UNKNOWN_FILE_EXTENSION" &&
configPath.endsWith(".ts")) {
const realPath = await getRealPath(fileURLToPath(configPath));
if (compiledConfigFile.has(realPath)) {
return compiledConfigFile.get(realPath);
}
const { tsImport } = await import("tsx/esm/api");
const config = await tsImport(configPath, import.meta.url);
compiledConfigFile.set(realPath, config);
return config;
}
throw error;
}
}
catch (error) {
ensureError(error);
switch (error.name) {
case "TransformError":
const errors = error.message
.split("\n")
.filter((line) => line.includes(path.basename(configPath)))
// For example: /.../hardhat.config.ts:86:5: ERROR: Expected "}" but found "\"community-plugin\""
.map((line) => line.split(":"))
.map(([_path, line, _char, _code, ...message]) => `* Syntax error in line ${line}: ${message.join(":").trim()}`);
throw new HardhatError(HardhatError.ERRORS.CORE.GENERAL.INVALID_CONFIG_FILE, {
configPath,
errors: `\t${errors.join("\n\t")}`,
});
default:
throw error;
}
}
}
//# sourceMappingURL=config-loading.js.map