hardhat 3.5.0 → 3.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/src/internal/builtin-plugins/solidity/build-system/build-system.d.ts +1 -4
  3. package/dist/src/internal/builtin-plugins/solidity/build-system/build-system.d.ts.map +1 -1
  4. package/dist/src/internal/builtin-plugins/solidity/build-system/build-system.js +1 -11
  5. package/dist/src/internal/builtin-plugins/solidity/build-system/build-system.js.map +1 -1
  6. package/dist/src/internal/builtin-plugins/solidity/build-system/warning-suppression.d.ts +6 -10
  7. package/dist/src/internal/builtin-plugins/solidity/build-system/warning-suppression.d.ts.map +1 -1
  8. package/dist/src/internal/builtin-plugins/solidity/build-system/warning-suppression.js +54 -43
  9. package/dist/src/internal/builtin-plugins/solidity/build-system/warning-suppression.js.map +1 -1
  10. package/dist/src/internal/builtin-plugins/solidity/hook-handlers/hre.d.ts.map +1 -1
  11. package/dist/src/internal/builtin-plugins/solidity/hook-handlers/hre.js +2 -1
  12. package/dist/src/internal/builtin-plugins/solidity/hook-handlers/hre.js.map +1 -1
  13. package/dist/src/internal/cli/error-handler.d.ts +11 -2
  14. package/dist/src/internal/cli/error-handler.d.ts.map +1 -1
  15. package/dist/src/internal/cli/error-handler.js +72 -46
  16. package/dist/src/internal/cli/error-handler.js.map +1 -1
  17. package/dist/src/internal/cli/init/init.d.ts.map +1 -1
  18. package/dist/src/internal/cli/init/init.js +5 -3
  19. package/dist/src/internal/cli/init/init.js.map +1 -1
  20. package/dist/src/internal/cli/init/package-manager.d.ts +7 -1
  21. package/dist/src/internal/cli/init/package-manager.d.ts.map +1 -1
  22. package/dist/src/internal/cli/init/package-manager.js +14 -2
  23. package/dist/src/internal/cli/init/package-manager.js.map +1 -1
  24. package/dist/src/internal/cli/main.js +1 -1
  25. package/dist/src/internal/cli/main.js.map +1 -1
  26. package/dist/src/internal/cli/telemetry/error-classification/classifier.d.ts +2 -1
  27. package/dist/src/internal/cli/telemetry/error-classification/classifier.d.ts.map +1 -1
  28. package/dist/src/internal/cli/telemetry/error-classification/classifier.js +2 -1
  29. package/dist/src/internal/cli/telemetry/error-classification/classifier.js.map +1 -1
  30. package/dist/src/internal/using-hardhat2-plugin-errors.d.ts.map +1 -1
  31. package/dist/src/internal/using-hardhat2-plugin-errors.js +8 -11
  32. package/dist/src/internal/using-hardhat2-plugin-errors.js.map +1 -1
  33. package/package.json +1 -1
  34. package/src/internal/builtin-plugins/solidity/build-system/build-system.ts +2 -15
  35. package/src/internal/builtin-plugins/solidity/build-system/warning-suppression.ts +81 -59
  36. package/src/internal/builtin-plugins/solidity/hook-handlers/hre.ts +2 -1
  37. package/src/internal/cli/error-handler.ts +103 -72
  38. package/src/internal/cli/init/init.ts +5 -0
  39. package/src/internal/cli/init/package-manager.ts +18 -1
  40. package/src/internal/cli/main.ts +1 -1
  41. package/src/internal/cli/telemetry/error-classification/classifier.ts +2 -1
  42. package/src/internal/using-hardhat2-plugin-errors.ts +8 -11
  43. package/templates/hardhat-3/01-node-test-runner-viem/package.json +2 -2
  44. package/templates/hardhat-3/02-mocha-ethers/package.json +1 -1
  45. package/templates/hardhat-3/03-minimal/package.json +1 -1
@@ -10,21 +10,18 @@ export class UsingHardhat2PluginError extends CustomError {
10
10
  if (callerPath !== undefined) {
11
11
  message = `You are trying to use a Hardhat 2 plugin in a Hardhat 3 project.
12
12
 
13
- This file is part of a Hardhat 2 plugin calling an API that was removed in Hardhat 3: ${styleText("bold", callerPath)}
14
-
15
- Please read https://hardhat.org/migrate-from-hardhat2 to learn how to migrate your project to Hardhat 3.
16
- `;
13
+ This file is part of a Hardhat 2 plugin calling an API that was removed in Hardhat 3: ${styleText("bold", callerPath)}`;
17
14
  }
18
15
  else {
19
- message = `You are trying to use a Hardhat 2 plugin in a Hardhat 3 project.
20
-
21
- Check the stack trace below to identify which plugin is causing this.
22
-
23
- Please read https://hardhat.org/migrate-from-hardhat2 to learn how to migrate your project to Hardhat 3.
24
- `;
16
+ message = `You are trying to use a Hardhat 2 plugin in a Hardhat 3 project.`;
25
17
  }
26
18
  super(message);
27
- this.callerRelativePath = callerPath;
19
+ // Use a non-enumerable property so that `console.error(error)` does not
20
+ // print `{ callerRelativePath: '…' }` after the stack trace.
21
+ Object.defineProperty(this, "callerRelativePath", {
22
+ value: callerPath,
23
+ enumerable: false,
24
+ });
28
25
  }
29
26
  }
30
27
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"using-hardhat2-plugin-errors.js","sourceRoot":"","sources":["../../../src/internal/using-hardhat2-plugin-errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtC,OAAO,EAAE,WAAW,EAAE,MAAM,sCAAsC,CAAC;AACnE,OAAO,EAAE,WAAW,EAAE,MAAM,qCAAqC,CAAC;AAElE,MAAM,OAAO,wBAAyB,SAAQ,WAAW;IACvC,kBAAkB,CAAqB;IACvD;QACE,MAAM,UAAU,GAAG,qBAAqB,EAAE,CAAC;QAE3C,IAAI,OAAe,CAAC;QACpB,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;YAC7B,OAAO,GAAG;;wFAEwE,SAAS,CAAC,MAAM,EAAE,UAAU,CAAC;;;CAGpH,CAAC;QACE,CAAC;aAAM,CAAC;YACN,OAAO,GAAG;;;;;CAKf,CAAC;QACE,CAAC;QAED,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,kBAAkB,GAAG,UAAU,CAAC;IACvC,CAAC;CACF;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,UAAU,qBAAqB,CAAC,QAAgB,CAAC;IACrD,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,IAAI,KAAK,EAAE,CAAC,KAAK,CAAC;QAChC,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,OAAO,SAAS,CAAC;QACnB,CAAC;QAED,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAChC,MAAM,UAAU,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC;QAChC,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;YAC7B,OAAO,SAAS,CAAC;QACnB,CAAC;QAED;;;;;;;;WAQG;QACH,MAAM,sBAAsB,GAC1B,iDAAiD,CAAC;QAEpD,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,sBAAsB,CAAC,CAAC;QAC9D,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,SAAS,EAAE,CAAC;YAC7C,OAAO,SAAS,CAAC;QACnB,CAAC;QAED,IAAI,QAAQ,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QAExB,4CAA4C;QAC5C,IAAI,QAAQ,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YACnC,QAAQ,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC;QACrC,CAAC;QAED,OAAO,WAAW,CAAC,QAAQ,CAAC,CAAC;IAC/B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,MAAM,UAAU,6BAA6B;IAC3C;kEAC8D;IAC9D,MAAM,IAAI,wBAAwB,EAAE,CAAC;AACvC,CAAC"}
1
+ {"version":3,"file":"using-hardhat2-plugin-errors.js","sourceRoot":"","sources":["../../../src/internal/using-hardhat2-plugin-errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtC,OAAO,EAAE,WAAW,EAAE,MAAM,sCAAsC,CAAC;AACnE,OAAO,EAAE,WAAW,EAAE,MAAM,qCAAqC,CAAC;AAElE,MAAM,OAAO,wBAAyB,SAAQ,WAAW;IACvC,kBAAkB,CAAqB;IACvD;QACE,MAAM,UAAU,GAAG,qBAAqB,EAAE,CAAC;QAE3C,IAAI,OAAe,CAAC;QACpB,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;YAC7B,OAAO,GAAG;;wFAEwE,SAAS,CAAC,MAAM,EAAE,UAAU,CAAC,EAAE,CAAC;QACpH,CAAC;aAAM,CAAC;YACN,OAAO,GAAG,kEAAkE,CAAC;QAC/E,CAAC;QAED,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,wEAAwE;QACxE,6DAA6D;QAC7D,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,oBAAoB,EAAE;YAChD,KAAK,EAAE,UAAU;YACjB,UAAU,EAAE,KAAK;SAClB,CAAC,CAAC;IACL,CAAC;CACF;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,UAAU,qBAAqB,CAAC,QAAgB,CAAC;IACrD,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,IAAI,KAAK,EAAE,CAAC,KAAK,CAAC;QAChC,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,OAAO,SAAS,CAAC;QACnB,CAAC;QAED,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAChC,MAAM,UAAU,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC;QAChC,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;YAC7B,OAAO,SAAS,CAAC;QACnB,CAAC;QAED;;;;;;;;WAQG;QACH,MAAM,sBAAsB,GAC1B,iDAAiD,CAAC;QAEpD,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,sBAAsB,CAAC,CAAC;QAC9D,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,SAAS,EAAE,CAAC;YAC7C,OAAO,SAAS,CAAC;QACnB,CAAC;QAED,IAAI,QAAQ,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QAExB,4CAA4C;QAC5C,IAAI,QAAQ,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YACnC,QAAQ,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC;QACrC,CAAC;QAED,OAAO,WAAW,CAAC,QAAQ,CAAC,CAAC;IAC/B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,MAAM,UAAU,6BAA6B;IAC3C;kEAC8D;IAC9D,MAAM,IAAI,wBAAwB,EAAE,CAAC;AACvC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hardhat",
3
- "version": "3.5.0",
3
+ "version": "3.5.1",
4
4
  "description": "Hardhat is an extensible developer tool that helps smart contract developers increase productivity by reliably bringing together the tools they want.",
5
5
  "homepage": "https://hardhat.org",
6
6
  "repository": {
@@ -95,21 +95,6 @@ export function isSolcSolidityCompilerConfig(
95
95
  return config.type === undefined || config.type === "solc";
96
96
  }
97
97
 
98
- // Compiler warnings to suppress from build output.
99
- // Each rule specifies a warning message and the source file it applies to.
100
- // This allows suppressing known warnings from internal files (e.g., console.sol)
101
- // while still showing the same warning type from user code.
102
- export const SUPPRESSED_WARNINGS: Array<{
103
- message: string;
104
- sourceFile: string;
105
- }> = [
106
- {
107
- message:
108
- "Natspec memory-safe-assembly special comment for inline assembly is deprecated and scheduled for removal. Use the memory-safe block annotation instead.",
109
- sourceFile: path.normalize("hardhat/console.sol"),
110
- },
111
- ];
112
-
113
98
  interface CompilationResult {
114
99
  compilationJob: CompilationJob;
115
100
  compilerOutput: CompilerOutput;
@@ -123,6 +108,7 @@ export interface SolidityBuildSystemOptions {
123
108
  readonly artifactsPath: string;
124
109
  readonly cachePath: string;
125
110
  readonly solidityTestsPath: string;
111
+ readonly coverage: boolean;
126
112
  }
127
113
 
128
114
  /**
@@ -1417,6 +1403,7 @@ export class SolidityBuildSystemImplementation implements SolidityBuildSystem {
1417
1403
  msg,
1418
1404
  this.#options.solidityTestsPath,
1419
1405
  this.#options.projectRoot,
1406
+ this.#options.coverage,
1420
1407
  );
1421
1408
  }
1422
1409
 
@@ -1,97 +1,119 @@
1
1
  import path from "node:path";
2
2
 
3
- // Compiler warnings to suppress from build output.
4
- // Supports two types of suppression rules:
5
- //
6
- // 1. scope: 'specific-file' - Suppress warnings from specific file paths
7
- // - Use this to suppress known warnings from internal/library files (e.g., console.sol)
8
- // - The same warning type will still be shown for user code
9
- //
10
- // 2. scope: 'test-files' - Suppress warnings from all test files
11
- // - Test files are identified as:
12
- // * Files ending in .t.sol (e.g., Counter.t.sol)
13
- // * Files inside test/contracts/ directory
14
- // (e.g., test/contracts/Example.sol)
15
- // - Use this for warnings that are acceptable in test code but not in production code
16
- // (e.g., missing SPDX license identifiers or pragma statements)
17
- export const SUPPRESSED_WARNINGS: Array<
18
- | {
19
- message: string;
20
- scope: "specific-file";
21
- filePath: string;
22
- }
23
- | {
24
- message: string;
25
- scope: "test-files";
26
- }
27
- > = [
3
+ import { COVERAGE_LIBRARY_FILE_NAME } from "@nomicfoundation/edr";
4
+
5
+ export const NATSPEC_MEMORY_SAFE_ASSEMBLY_WARNING =
6
+ "Natspec memory-safe-assembly special comment for inline assembly is deprecated and scheduled for removal. Use the memory-safe block annotation instead.";
7
+ export const SPDX_WARNING = "SPDX license identifier not provided";
8
+ export const PRAGMA_WARNING =
9
+ "Source file does not specify required compiler version";
10
+ const CONTRACT_SIZE_WARNING = "Contract code size is";
11
+
12
+ // Suppression rules are grouped by scope. Each array has its own match logic
13
+ // in `shouldSuppressWarning` function; add new entries to the array that fits, or add
14
+ // a new array + block for a new scope.
15
+
16
+ // Warnings tied to a specific internal file (e.g. console.sol). Suppressed
17
+ // only when the warning points at that file.
18
+ const SPECIFIC_FILE_RULES: ReadonlyArray<{
19
+ message: string;
20
+ filePath: string;
21
+ }> = [
28
22
  {
29
- message:
30
- "Natspec memory-safe-assembly special comment for inline assembly is deprecated and scheduled for removal. Use the memory-safe block annotation instead.",
31
- scope: "specific-file",
23
+ message: NATSPEC_MEMORY_SAFE_ASSEMBLY_WARNING,
32
24
  // Normalize to handle different OS path separators
33
25
  filePath: path.normalize("hardhat/console.sol"),
34
26
  },
27
+ ];
28
+
29
+ // Warnings acceptable in test files. Suppressed when the warning points at a
30
+ // file ending in `.t.sol` or inside the configured Solidity test directory.
31
+ const TEST_FILE_WARNING_MESSAGES: readonly string[] = [
32
+ SPDX_WARNING,
33
+ PRAGMA_WARNING,
34
+ ];
35
+
36
+ // Warnings suppressed only when running with `--coverage`. An entry with no
37
+ // `filePath` matches the message anywhere (e.g. contract-size warnings that
38
+ // fire on user files as a side effect of instrumentation); an entry with a
39
+ // `filePath` only matches when the diagnostic also points at that file
40
+ // (e.g. the injected coverage library file, which users can't edit).
41
+ const COVERAGE_MODE_RULES: ReadonlyArray<{
42
+ message: string;
43
+ filePath?: string;
44
+ }> = [
45
+ { message: CONTRACT_SIZE_WARNING },
35
46
  {
36
- message: "SPDX license identifier not provided",
37
- scope: "test-files",
38
- },
39
- {
40
- message: "Source file does not specify required compiler version",
41
- scope: "test-files",
47
+ message: NATSPEC_MEMORY_SAFE_ASSEMBLY_WARNING,
48
+ filePath: COVERAGE_LIBRARY_FILE_NAME,
42
49
  },
43
50
  ];
44
51
 
45
52
  /**
46
- * Determines if a compiler warning should be suppressed based on the suppression rules.
53
+ * Determines if a compiler warning should be suppressed.
47
54
  *
48
55
  * @param errorMessage - The formatted error message from the compiler
49
56
  * @param absoluteSolidityTestsPath - Absolute path to the Solidity test directory
50
57
  * @param absoluteProjectRoot - Absolute path to the project root
58
+ * @param coverage - Whether the build is running with `--coverage` enabled
51
59
  * @returns true if the warning should be suppressed, false otherwise
52
60
  */
53
61
  export function shouldSuppressWarning(
54
62
  errorMessage: string,
55
63
  absoluteSolidityTestsPath: string,
56
64
  absoluteProjectRoot: string,
65
+ coverage: boolean,
57
66
  ): boolean {
58
- // Compute relative path from project root to test directory.
59
- // Example:
60
- // absoluteSolidityTestsPath: /workspaces/hardhat-4/packages/example-project/test/contracts
61
- // absoluteProjectRoot: /workspaces/hardhat-4/packages/example-project
62
- // relativeTestPath: test/contracts/ - note the addition of the `/`
63
- // to avoid partial matches, e.g.: test/contractsUtils/
64
- const relativeTestPath = path.join(
65
- path.relative(absoluteProjectRoot, absoluteSolidityTestsPath),
66
- "/",
67
- );
68
-
69
- return SUPPRESSED_WARNINGS.some((rule) => {
70
- if (!errorMessage.includes(rule.message)) {
71
- return false;
72
- }
67
+ // Warnings suppressed only when running with `--coverage`.
68
+ if (
69
+ coverage &&
70
+ COVERAGE_MODE_RULES.some(
71
+ (rule) =>
72
+ errorMessage.includes(rule.message) &&
73
+ (rule.filePath === undefined || errorMessage.includes(rule.filePath)),
74
+ )
75
+ ) {
76
+ return true;
77
+ }
73
78
 
74
- if (rule.scope === "specific-file") {
75
- return errorMessage.includes(rule.filePath);
76
- }
79
+ // Warnings tied to a specific internal file (e.g. console.sol).
80
+ if (
81
+ SPECIFIC_FILE_RULES.some(
82
+ (rule) =>
83
+ errorMessage.includes(rule.message) &&
84
+ errorMessage.includes(rule.filePath),
85
+ )
86
+ ) {
87
+ return true;
88
+ }
77
89
 
78
- // Check if the message contains a path to a test file
90
+ // Warnings allowed in test files.
91
+ if (TEST_FILE_WARNING_MESSAGES.some((m) => errorMessage.includes(m))) {
79
92
  // Test files are identified by:
80
93
  // - Ending in .t.sol (e.g., Counter.t.sol)
81
94
  // - Being inside the configured Solidity test directory
82
-
83
95
  if (/\.t\.sol(:|$|\s)/.test(errorMessage)) {
84
96
  return true;
85
97
  }
86
98
 
87
- // Extract file path from error message
99
+ // Compute relative path from project root to test directory.
100
+ // Example:
101
+ // absoluteSolidityTestsPath: /workspaces/hardhat-4/packages/example-project/test/contracts
102
+ // absoluteProjectRoot: /workspaces/hardhat-4/packages/example-project
103
+ // relativeTestPath: test/contracts/ - note the addition of the `/`
104
+ // to avoid partial matches, e.g.: test/contractsUtils/
105
+ const relativeTestPath = path.join(
106
+ path.relative(absoluteProjectRoot, absoluteSolidityTestsPath),
107
+ "/",
108
+ );
109
+
110
+ // Extract file path from error message.
88
111
  // Format: "Warning: message\n --> path/to/file.sol:line:column:"
89
112
  const pathMatches = errorMessage.match(/-->\s+([^\s:]+\.sol)/);
90
-
91
113
  if (pathMatches !== null) {
92
114
  return pathMatches[1].includes(relativeTestPath);
93
115
  }
116
+ }
94
117
 
95
- return false;
96
- });
118
+ return false;
97
119
  }
@@ -152,7 +152,7 @@ class LazySolidityBuildSystem implements SolidityBuildSystem {
152
152
 
153
153
  export default async (): Promise<Partial<HardhatRuntimeEnvironmentHooks>> => {
154
154
  return {
155
- created: async (_context, hre) => {
155
+ created: async (context, hre) => {
156
156
  hre.solidity = new LazySolidityBuildSystem(hre.hooks, {
157
157
  solidityConfig: hre.config.solidity,
158
158
  projectRoot: hre.config.paths.root,
@@ -160,6 +160,7 @@ export default async (): Promise<Partial<HardhatRuntimeEnvironmentHooks>> => {
160
160
  artifactsPath: hre.config.paths.artifacts,
161
161
  cachePath: hre.config.paths.cache,
162
162
  solidityTestsPath: hre.config.paths.tests.solidity,
163
+ coverage: context.globalOptions.coverage,
163
164
  });
164
165
  },
165
166
  };
@@ -1,3 +1,5 @@
1
+ import type * as ClassifierT from "./telemetry/error-classification/classifier.js";
2
+
1
3
  import { styleText } from "node:util";
2
4
 
3
5
  import {
@@ -6,19 +8,24 @@ import {
6
8
  } from "@nomicfoundation/hardhat-errors";
7
9
 
8
10
  import { HARDHAT_NAME, HARDHAT_WEBSITE_URL } from "../constants.js";
9
- import { UsingHardhat2PluginError } from "../using-hardhat2-plugin-errors.js";
11
+
12
+ // The classifier may import many unrelated things top-level to do its job, so
13
+ // we load it lazily.
14
+ let classifierModule: typeof ClassifierT | undefined;
10
15
 
11
16
  /**
12
17
  * The different categories of errors that can be handled by hardhat cli.
13
18
  * Each category has a different way of being formatted and displayed.
14
19
  * To add new categories, add a new entry to this enum and update the
15
- * {@link getErrorWithCategory} and {@link getErrorMessages} functions
16
- * accordingly.
20
+ * {@link getErrorWithCategory} function and the switch inside
21
+ * {@link printErrorMessages} accordingly.
17
22
  */
18
23
  enum ErrorCategory {
19
24
  HARDHAT = "HARDHAT",
20
25
  PLUGIN = "PLUGIN",
21
26
  COMMUNITY_PLUGIN = "COMMUNITY_PLUGIN",
27
+ HH2_TO_HH3_MIGRATION = "HH2_TO_HH3_MIGRATION",
28
+ CJS_TO_ESM_MIGRATION = "CJS_TO_ESM_MIGRATION",
22
29
  OTHER = "OTHER",
23
30
  }
24
31
 
@@ -35,55 +42,91 @@ type ErrorWithCategory =
35
42
  category: ErrorCategory.COMMUNITY_PLUGIN;
36
43
  categorizedError: HardhatPluginError;
37
44
  }
45
+ | {
46
+ category: ErrorCategory.HH2_TO_HH3_MIGRATION;
47
+ categorizedError: Error;
48
+ }
49
+ | {
50
+ category: ErrorCategory.CJS_TO_ESM_MIGRATION;
51
+ categorizedError: Error;
52
+ }
38
53
  | {
39
54
  category: ErrorCategory.OTHER;
40
55
  categorizedError: unknown;
41
56
  };
42
57
 
43
- /**
44
- * The different messages that can be displayed for each category of errors.
45
- * - `formattedErrorMessage`: the main error message that is always displayed.
46
- * - `showMoreInfoMessage`: an optional message that can be displayed to
47
- * provide more information about the error. It is only displayed when stack
48
- * traces are hidden.
49
- * - `postErrorStackTraceMessage` an optional message that can be displayed
50
- * after the stack trace. It is only displayed when stack traces are shown.
51
- */
52
- interface ErrorMessages {
53
- formattedErrorMessage: string;
54
- showMoreInfoMessage?: string;
55
- postErrorStackTraceMessage?: string;
56
- }
57
-
58
58
  /**
59
59
  * Formats and logs error messages based on the category the error belongs to.
60
60
  *
61
+ * For each category, we produce up to three pieces:
62
+ * - `formattedErrorMessage`: the main error message that is always displayed.
63
+ * - `showMoreInfoMessage`: an optional message that points the user at more
64
+ * info. It is only displayed when the stack trace is hidden.
65
+ * - `postErrorStackTraceMessage`: an optional message that is displayed after
66
+ * the stack trace.
67
+ *
61
68
  * @param error the error to handle. Supported categories are defined in
62
69
  * {@link ErrorCategory}.
63
70
  * @param shouldShowStackTraces whether to show stack traces or not. If true,
64
71
  * the stack trace is always shown. If false, the stack trace is only shown for
65
- * errors of category {@link ErrorCategory.OTHER}.
72
+ * errors of category {@link ErrorCategory.OTHER},
73
+ * {@link ErrorCategory.HH2_TO_HH3_MIGRATION}, and
74
+ * {@link ErrorCategory.CJS_TO_ESM_MIGRATION}.
66
75
  * @param print the function used to print the error message, defaults to
67
76
  * `console.error`. Useful for testing to capture error messages.
68
77
  */
69
- export function printErrorMessages(
78
+ export async function printErrorMessages(
70
79
  error: Error,
71
80
  shouldShowStackTraces: boolean = false,
72
81
  print: (message: string | Error) => void = console.error,
73
- ): void {
74
- if (error instanceof UsingHardhat2PluginError) {
75
- printUsingHardhat2Error(error, print);
76
- return;
82
+ ): Promise<void> {
83
+ const { category, categorizedError } = await getErrorWithCategory(error);
84
+
85
+ let formattedErrorMessage: string;
86
+ let showMoreInfoMessage: string | undefined;
87
+ let postErrorStackTraceMessage: string | undefined;
88
+
89
+ switch (category) {
90
+ case ErrorCategory.HARDHAT:
91
+ formattedErrorMessage = `${styleText(["red", "bold"], `Error ${categorizedError.errorCode}:`)} ${categorizedError.formattedMessage}`;
92
+ showMoreInfoMessage = `For more info go to ${HARDHAT_WEBSITE_URL}${categorizedError.errorCode} or run ${HARDHAT_NAME} with --show-stack-traces`;
93
+ break;
94
+ case ErrorCategory.PLUGIN:
95
+ formattedErrorMessage = `${styleText(["red", "bold"], `Error ${categorizedError.errorCode} in plugin ${categorizedError.pluginId}:`)} ${categorizedError.formattedMessage}`;
96
+ showMoreInfoMessage = `For more info go to ${HARDHAT_WEBSITE_URL}${categorizedError.errorCode} or run ${HARDHAT_NAME} with --show-stack-traces`;
97
+ break;
98
+ case ErrorCategory.COMMUNITY_PLUGIN:
99
+ formattedErrorMessage = `${styleText(["red", "bold"], `Error in community plugin ${categorizedError.pluginId}:`)} ${categorizedError.message}`;
100
+ showMoreInfoMessage = `For more info run ${HARDHAT_NAME} with --show-stack-traces`;
101
+ break;
102
+ case ErrorCategory.HH2_TO_HH3_MIGRATION:
103
+ formattedErrorMessage = styleText(
104
+ ["red", "bold"],
105
+ `Hardhat 3 migration error:`,
106
+ );
107
+ postErrorStackTraceMessage = `It looks like you are migrating from Hardhat 2 to Hardhat 3. The following error often shows up during this kind of migration.\nPlease read https://hardhat.org/migrate-from-hardhat2 to learn how to migrate your project to Hardhat 3.`;
108
+ break;
109
+ case ErrorCategory.CJS_TO_ESM_MIGRATION:
110
+ formattedErrorMessage = styleText(
111
+ ["red", "bold"],
112
+ `Hardhat 3 migration error:`,
113
+ );
114
+ postErrorStackTraceMessage = `It looks like you are migrating from CommonJS to ESM. The following error often shows up during this kind of migration.\nPlease read https://hardhat.org/migrate-to-esm to learn how to migrate your project to ESM.`;
115
+ break;
116
+ case ErrorCategory.OTHER:
117
+ formattedErrorMessage = styleText(
118
+ ["red", "bold"],
119
+ `An unexpected error occurred:`,
120
+ );
121
+ postErrorStackTraceMessage = `If you think this is a bug in Hardhat, please report it here: ${HARDHAT_WEBSITE_URL}report-bug`;
122
+ break;
77
123
  }
78
124
 
79
125
  const showStackTraces =
80
126
  shouldShowStackTraces ||
81
- getErrorWithCategory(error).category === ErrorCategory.OTHER;
82
- const {
83
- formattedErrorMessage,
84
- showMoreInfoMessage,
85
- postErrorStackTraceMessage,
86
- } = getErrorMessages(error);
127
+ category === ErrorCategory.OTHER ||
128
+ category === ErrorCategory.HH2_TO_HH3_MIGRATION ||
129
+ category === ErrorCategory.CJS_TO_ESM_MIGRATION;
87
130
 
88
131
  print(formattedErrorMessage);
89
132
 
@@ -100,7 +143,7 @@ export function printErrorMessages(
100
143
  }
101
144
  }
102
145
 
103
- function getErrorWithCategory(error: Error): ErrorWithCategory {
146
+ async function getErrorWithCategory(error: Error): Promise<ErrorWithCategory> {
104
147
  if (HardhatError.isHardhatError(error)) {
105
148
  if (error.pluginId === undefined) {
106
149
  return {
@@ -122,49 +165,37 @@ function getErrorWithCategory(error: Error): ErrorWithCategory {
122
165
  };
123
166
  }
124
167
 
168
+ if (classifierModule === undefined) {
169
+ classifierModule = await import(
170
+ "./telemetry/error-classification/classifier.js"
171
+ );
172
+ }
173
+
174
+ // Pass `ignoreDevelopmentTimeFilter=true` so the migration footer also shows
175
+ // when hardhat is run from inside its own monorepo — the dev-time filter is
176
+ // a telemetry concern (don't report to Sentry), unrelated to display routing.
177
+ const classifierCategory = classifierModule.classifyError(error, true);
178
+ if (
179
+ classifierCategory ===
180
+ classifierModule.ErrorCategory.HH2_TO_HH3_MIGRATION_ERROR
181
+ ) {
182
+ return {
183
+ category: ErrorCategory.HH2_TO_HH3_MIGRATION,
184
+ categorizedError: error,
185
+ };
186
+ }
187
+ if (
188
+ classifierCategory ===
189
+ classifierModule.ErrorCategory.CJS_TO_ESM_MIGRATION_ERROR
190
+ ) {
191
+ return {
192
+ category: ErrorCategory.CJS_TO_ESM_MIGRATION,
193
+ categorizedError: error,
194
+ };
195
+ }
196
+
125
197
  return {
126
198
  category: ErrorCategory.OTHER,
127
199
  categorizedError: error,
128
200
  };
129
201
  }
130
-
131
- function getErrorMessages(error: Error): ErrorMessages {
132
- const { category, categorizedError } = getErrorWithCategory(error);
133
- switch (category) {
134
- case ErrorCategory.HARDHAT:
135
- return {
136
- formattedErrorMessage: `${styleText(["red", "bold"], `Error ${categorizedError.errorCode}:`)} ${categorizedError.formattedMessage}`,
137
- showMoreInfoMessage: `For more info go to ${HARDHAT_WEBSITE_URL}${categorizedError.errorCode} or run ${HARDHAT_NAME} with --show-stack-traces`,
138
- };
139
- case ErrorCategory.PLUGIN:
140
- return {
141
- formattedErrorMessage: `${styleText(["red", "bold"], `Error ${categorizedError.errorCode} in plugin ${categorizedError.pluginId}:`)} ${categorizedError.formattedMessage}`,
142
- showMoreInfoMessage: `For more info go to ${HARDHAT_WEBSITE_URL}${categorizedError.errorCode} or run ${HARDHAT_NAME} with --show-stack-traces`,
143
- };
144
- case ErrorCategory.COMMUNITY_PLUGIN:
145
- return {
146
- formattedErrorMessage: `${styleText(["red", "bold"], `Error in community plugin ${categorizedError.pluginId}:`)} ${categorizedError.message}`,
147
- showMoreInfoMessage: `For more info run ${HARDHAT_NAME} with --show-stack-traces`,
148
- };
149
- case ErrorCategory.OTHER:
150
- return {
151
- formattedErrorMessage: styleText(
152
- ["red", "bold"],
153
- `An unexpected error occurred:`,
154
- ),
155
- postErrorStackTraceMessage: `If you think this is a bug in Hardhat, please report it here: ${HARDHAT_WEBSITE_URL}report-bug`,
156
- };
157
- }
158
- }
159
- function printUsingHardhat2Error(
160
- error: UsingHardhat2PluginError,
161
- print: (message: string | Error) => void = console.error,
162
- ): void {
163
- print(styleText(["red", "bold"], `Hardhat 3 installation error:`));
164
- print("");
165
- if (error.callerRelativePath !== undefined) {
166
- print(error.message);
167
- } else {
168
- print(error.stack);
169
- }
170
- }
@@ -35,6 +35,7 @@ import { sendErrorTelemetry } from "../telemetry/error-reporter/reporter.js";
35
35
  import {
36
36
  getDevDependenciesInstallationCommand,
37
37
  getPackageManager,
38
+ getVersion,
38
39
  installsPeerDependenciesByDefault,
39
40
  } from "./package-manager.js";
40
41
  import {
@@ -694,6 +695,8 @@ export async function installProjectDependencies({
694
695
  );
695
696
 
696
697
  const packageManager = getPackageManager();
698
+ const packageManagerVersion = await getVersion(workspace, packageManager);
699
+ const packageManagerMajorVersion = packageManagerVersion?.[0];
697
700
 
698
701
  // Find the template dev dependencies that are not already installed
699
702
  const templateDependencies = template.packageJson.devDependencies ?? {};
@@ -730,6 +733,7 @@ export async function installProjectDependencies({
730
733
  const command = getDevDependenciesInstallationCommand(
731
734
  packageManager,
732
735
  dependenciesToInstall,
736
+ packageManagerMajorVersion,
733
737
  );
734
738
  const commandString = command.join(" ");
735
739
 
@@ -788,6 +792,7 @@ export async function installProjectDependencies({
788
792
  const command = getDevDependenciesInstallationCommand(
789
793
  packageManager,
790
794
  dependenciesToUpdate,
795
+ packageManagerMajorVersion,
791
796
  );
792
797
  const commandString = command.join(" ");
793
798
 
@@ -104,11 +104,16 @@ export function getPackageManager(): PackageManager {
104
104
  *
105
105
  * @param packageManager The package manager to use.
106
106
  * @param dependencies The dependencies to install.
107
+ * @param packageManagerMajorVersion The major version of the package manager,
108
+ * if known. Used to opt into version-specific flags (e.g. pnpm 11's
109
+ * `--allow-build=esbuild`, required because `tsx` ships `esbuild` whose
110
+ * postinstall script is otherwise blocked).
107
111
  * @returns The installation command.
108
112
  */
109
113
  export function getDevDependenciesInstallationCommand(
110
114
  packageManager: PackageManager,
111
115
  dependencies: string[],
116
+ packageManagerMajorVersion?: number,
112
117
  ): string[] {
113
118
  const packageManagerToCommand: Record<PackageManager, string[]> = {
114
119
  npm: ["npm", "install", "--save-dev"],
@@ -118,6 +123,18 @@ export function getDevDependenciesInstallationCommand(
118
123
  bun: ["bun", "add", "--dev"],
119
124
  };
120
125
  const command = packageManagerToCommand[packageManager];
126
+
127
+ // NOTE: adding an unnecessary flag doesn't result in an error
128
+ // nor warning, so we don't check if hardhat or tsx are being
129
+ // installed.
130
+ if (
131
+ packageManager === "pnpm" &&
132
+ packageManagerMajorVersion !== undefined &&
133
+ packageManagerMajorVersion >= 11
134
+ ) {
135
+ command.push("--allow-build=esbuild");
136
+ }
137
+
121
138
  // We quote all the dependency identifiers so that they can be run on a shell
122
139
  // without semver symbols interfering with the command
123
140
  command.push(
@@ -205,7 +222,7 @@ export async function installsPeerDependenciesByDefault(
205
222
  }
206
223
  }
207
224
 
208
- async function getVersion(
225
+ export async function getVersion(
209
226
  workspace: string,
210
227
  packageManager: PackageManager,
211
228
  version?: string,
@@ -318,7 +318,7 @@ export async function main(
318
318
  }
319
319
  } catch (error) {
320
320
  ensureError(error);
321
- printErrorMessages(error, builtinGlobalOptions?.showStackTraces);
321
+ await printErrorMessages(error, builtinGlobalOptions?.showStackTraces);
322
322
 
323
323
  try {
324
324
  await sendErrorTelemetry(error);
@@ -70,7 +70,8 @@ import {
70
70
  * @param error The error to classify.
71
71
  * @param ignoreDevelopmentTimeFilter If true, the classifier will ignore the
72
72
  * development-time filter, which is used to exclude errors that happen during
73
- * development of Hardhat itself. This is only meant to be used for testing.
73
+ * development of Hardhat itself. Set this from display-side callers (where the
74
+ * dev-time skip is irrelevant) and from tests.
74
75
  * @returns The error category.
75
76
  */
76
77
  export function classifyError(
@@ -13,21 +13,18 @@ export class UsingHardhat2PluginError extends CustomError {
13
13
  if (callerPath !== undefined) {
14
14
  message = `You are trying to use a Hardhat 2 plugin in a Hardhat 3 project.
15
15
 
16
- This file is part of a Hardhat 2 plugin calling an API that was removed in Hardhat 3: ${styleText("bold", callerPath)}
17
-
18
- Please read https://hardhat.org/migrate-from-hardhat2 to learn how to migrate your project to Hardhat 3.
19
- `;
16
+ This file is part of a Hardhat 2 plugin calling an API that was removed in Hardhat 3: ${styleText("bold", callerPath)}`;
20
17
  } else {
21
- message = `You are trying to use a Hardhat 2 plugin in a Hardhat 3 project.
22
-
23
- Check the stack trace below to identify which plugin is causing this.
24
-
25
- Please read https://hardhat.org/migrate-from-hardhat2 to learn how to migrate your project to Hardhat 3.
26
- `;
18
+ message = `You are trying to use a Hardhat 2 plugin in a Hardhat 3 project.`;
27
19
  }
28
20
 
29
21
  super(message);
30
- this.callerRelativePath = callerPath;
22
+ // Use a non-enumerable property so that `console.error(error)` does not
23
+ // print `{ callerRelativePath: '…' }` after the stack trace.
24
+ Object.defineProperty(this, "callerRelativePath", {
25
+ value: callerPath,
26
+ enumerable: false,
27
+ });
31
28
  }
32
29
  }
33
30