Skip to content

Commit a72e883

Browse files
authored
Merge pull request #5371 from NomicFoundation/check-missing-deps
Check for missing dependencies when failing to import a file action
2 parents a5ca777 + 34f171f commit a72e883

File tree

3 files changed

+96
-13
lines changed

3 files changed

+96
-13
lines changed

v-next/core/src/internal/plugins/detect-plugin-npm-dependency-problems.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,13 @@ import semver from "semver";
1313
* @param plugin - the plugin to be validated
1414
* @param basePathForNpmResolution - the directory path to use for node module resolution, defaulting to `process.cwd()`
1515
* @throws {HardhatError} with descriptor:
16-
* - {@link ERRORS.ARTIFACTS.PLUGIN_NOT_INSTALLED} if the plugin is not installed as an npm package
17-
* - {@link ERRORS.ARTIFACTS.PLUGIN_MISSING_DEPENDENCY} if the plugin's package peer dependency is not installed
18-
* - {@link ERRORS.ARTIFACTS.DEPENDENCY_VERSION_MISMATCH} if the plugin's package peer dependency is installed but has the wrong version
16+
* - {@link HardhatError.ERRORS.PLUGINS.PLUGIN_NOT_INSTALLED} if the plugin is not installed as an npm package
17+
* - {@link HardhatError.ERRORS.PLUGINS.PLUGIN_MISSING_DEPENDENCY} if the plugin's package peer dependency is not installed
18+
* - {@link HardhatError.ERRORS.PLUGINS.DEPENDENCY_VERSION_MISMATCH} if the plugin's package peer dependency is installed but has the wrong version
1919
*/
2020
export async function detectPluginNpmDependencyProblems(
2121
plugin: HardhatPlugin,
22-
basePathForNpmResolution: string,
22+
basePathForNpmResolution: string = process.cwd(),
2323
): Promise<void> {
2424
if (plugin.npmPackage === undefined) {
2525
return;

v-next/core/src/internal/tasks/resolved-task.ts

+26-6
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,14 @@ import type {
1111
TaskParameter,
1212
} from "../../types/tasks.js";
1313

14-
import { HardhatError } from "@nomicfoundation/hardhat-errors";
14+
import {
15+
HardhatError,
16+
assertHardhatInvariant,
17+
} from "@nomicfoundation/hardhat-errors";
1518
import { ensureError } from "@nomicfoundation/hardhat-utils/error";
1619

1720
import { isParameterValueValid } from "../parameters.js";
21+
import { detectPluginNpmDependencyProblems } from "../plugins/detect-plugin-npm-dependency-problems.js";
1822

1923
import { formatTaskId } from "./utils.js";
2024

@@ -126,11 +130,13 @@ export class ResolvedTask implements Task {
126130
): Promise<any> => {
127131
// The first action may be empty if the task was originally an empty task
128132
const currentAction = this.actions[currentIndex].action ?? (() => {});
129-
130133
const actionFn =
131134
typeof currentAction === "function"
132135
? currentAction
133-
: await this.#resolveFileAction(currentAction, this.id);
136+
: await this.#resolveFileAction(
137+
currentAction,
138+
this.actions[currentIndex].pluginId,
139+
);
134140

135141
if (currentIndex === 0) {
136142
/* eslint-disable-next-line @typescript-eslint/consistent-type-assertions --
@@ -237,18 +243,32 @@ export class ResolvedTask implements Task {
237243
*/
238244
async #resolveFileAction(
239245
actionFileUrl: string,
240-
taskId: string[],
246+
actionPluginId?: string,
241247
): Promise<NewTaskActionFunction | TaskOverrideActionFunction> {
242248
let resolvedActionFn;
243249
try {
244250
resolvedActionFn = await import(actionFileUrl);
245251
} catch (error) {
246252
ensureError(error);
253+
254+
if (actionPluginId !== undefined) {
255+
const plugin = this.#hre.config.plugins.find(
256+
(p) => p.id === actionPluginId,
257+
);
258+
259+
assertHardhatInvariant(
260+
plugin !== undefined,
261+
`Plugin with id ${actionPluginId} not found.`,
262+
);
263+
264+
await detectPluginNpmDependencyProblems(plugin);
265+
}
266+
247267
throw new HardhatError(
248268
HardhatError.ERRORS.TASK_DEFINITIONS.INVALID_ACTION_URL,
249269
{
250270
action: actionFileUrl,
251-
task: formatTaskId(taskId),
271+
task: formatTaskId(this.id),
252272
},
253273
error,
254274
);
@@ -259,7 +279,7 @@ export class ResolvedTask implements Task {
259279
HardhatError.ERRORS.TASK_DEFINITIONS.INVALID_ACTION,
260280
{
261281
action: actionFileUrl,
262-
task: formatTaskId(taskId),
282+
task: formatTaskId(this.id),
263283
},
264284
);
265285
}

v-next/core/test/internal/tasks/task-manager.ts

+66-3
Original file line numberDiff line numberDiff line change
@@ -1493,7 +1493,7 @@ describe("TaskManagerImplementation", () => {
14931493
);
14941494
});
14951495

1496-
it("should throw if an action url is provided and the module can't be resolved", async () => {
1496+
it("should throw if an action url is provided but the corresponding module can't be resolved", async () => {
14971497
const hre = await createHardhatRuntimeEnvironment({
14981498
plugins: [
14991499
{
@@ -1520,7 +1520,70 @@ describe("TaskManagerImplementation", () => {
15201520
);
15211521
});
15221522

1523-
it("should throw if an action url is provided and the module doesn't have a default export", async () => {
1523+
/**
1524+
* There are multiple scenarios where detectPluginNpmDependencyProblems
1525+
* can throw an error. We're not trying to test all of them, just verify
1526+
* that the logic is being called and that the error is being thrown.
1527+
*/
1528+
it("should throw if an action url is provided but the corresponding module can't be resolved due to a missing package", async () => {
1529+
const nonInstalledPackageActionUrl = import.meta.resolve(
1530+
"./fixture-projects/not-installed-package/index.js",
1531+
);
1532+
1533+
// the missing dependency is used in the NEW_TASK action
1534+
let hre = await createHardhatRuntimeEnvironment({
1535+
plugins: [
1536+
{
1537+
id: "plugin1",
1538+
npmPackage: "non-installed-package",
1539+
tasks: [
1540+
new NewTaskDefinitionBuilderImplementation("task1")
1541+
.setAction(nonInstalledPackageActionUrl)
1542+
.build(),
1543+
],
1544+
},
1545+
],
1546+
});
1547+
1548+
await assert.rejects(
1549+
hre.tasks.getTask("task1").run({}),
1550+
new HardhatError(HardhatError.ERRORS.PLUGINS.PLUGIN_NOT_INSTALLED, {
1551+
pluginId: "plugin1",
1552+
}),
1553+
);
1554+
1555+
// the missing dependency is used in the TASK_OVERRIDE action
1556+
hre = await createHardhatRuntimeEnvironment({
1557+
plugins: [
1558+
{
1559+
id: "plugin1",
1560+
tasks: [
1561+
new NewTaskDefinitionBuilderImplementation("task1")
1562+
.setAction(() => {})
1563+
.build(),
1564+
],
1565+
},
1566+
{
1567+
id: "plugin2",
1568+
npmPackage: "non-installed-package",
1569+
tasks: [
1570+
new TaskOverrideDefinitionBuilderImplementation("task1")
1571+
.setAction(nonInstalledPackageActionUrl)
1572+
.build(),
1573+
],
1574+
},
1575+
],
1576+
});
1577+
1578+
await assert.rejects(
1579+
hre.tasks.getTask("task1").run({}),
1580+
new HardhatError(HardhatError.ERRORS.PLUGINS.PLUGIN_NOT_INSTALLED, {
1581+
pluginId: "plugin2",
1582+
}),
1583+
);
1584+
});
1585+
1586+
it("should throw if an action url is provided and the corresponding module doesn't have a default export", async () => {
15241587
const actionUrl = import.meta.resolve(
15251588
"./fixture-projects/file-actions/no-default.js",
15261589
);
@@ -1551,7 +1614,7 @@ describe("TaskManagerImplementation", () => {
15511614
);
15521615
});
15531616

1554-
it("should throw if an action url is provided and the module default export is not a function", async () => {
1617+
it("should throw if an action url is provided and the corresponding module default export is not a function", async () => {
15551618
const actionUrl = import.meta.resolve(
15561619
"./fixture-projects/file-actions/no-default-fn.js",
15571620
);

0 commit comments

Comments
 (0)