hardhat 3.1.10 → 3.1.12

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 (227) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/dist/src/internal/builtin-plugins/artifacts/artifact-manager.d.ts +2 -2
  3. package/dist/src/internal/builtin-plugins/artifacts/artifact-manager.d.ts.map +1 -1
  4. package/dist/src/internal/builtin-plugins/artifacts/artifact-manager.js.map +1 -1
  5. package/dist/src/internal/builtin-plugins/artifacts/hook-handlers/hre.d.ts.map +1 -1
  6. package/dist/src/internal/builtin-plugins/artifacts/hook-handlers/hre.js.map +1 -1
  7. package/dist/src/internal/builtin-plugins/coverage/helpers.d.ts +3 -5
  8. package/dist/src/internal/builtin-plugins/coverage/helpers.d.ts.map +1 -1
  9. package/dist/src/internal/builtin-plugins/coverage/helpers.js +7 -19
  10. package/dist/src/internal/builtin-plugins/coverage/helpers.js.map +1 -1
  11. package/dist/src/internal/builtin-plugins/coverage/hook-handlers/test.d.ts +7 -0
  12. package/dist/src/internal/builtin-plugins/coverage/hook-handlers/test.d.ts.map +1 -0
  13. package/dist/src/internal/builtin-plugins/coverage/hook-handlers/test.js +42 -0
  14. package/dist/src/internal/builtin-plugins/coverage/hook-handlers/test.js.map +1 -0
  15. package/dist/src/internal/builtin-plugins/coverage/index.d.ts.map +1 -1
  16. package/dist/src/internal/builtin-plugins/coverage/index.js +1 -0
  17. package/dist/src/internal/builtin-plugins/coverage/index.js.map +1 -1
  18. package/dist/src/internal/builtin-plugins/gas-analytics/function-gas-snapshots.d.ts +53 -0
  19. package/dist/src/internal/builtin-plugins/gas-analytics/function-gas-snapshots.d.ts.map +1 -0
  20. package/dist/src/internal/builtin-plugins/gas-analytics/function-gas-snapshots.js +288 -0
  21. package/dist/src/internal/builtin-plugins/gas-analytics/function-gas-snapshots.js.map +1 -0
  22. package/dist/src/internal/builtin-plugins/gas-analytics/gas-analytics-manager.d.ts +0 -1
  23. package/dist/src/internal/builtin-plugins/gas-analytics/gas-analytics-manager.d.ts.map +1 -1
  24. package/dist/src/internal/builtin-plugins/gas-analytics/gas-analytics-manager.js +2 -14
  25. package/dist/src/internal/builtin-plugins/gas-analytics/gas-analytics-manager.js.map +1 -1
  26. package/dist/src/internal/builtin-plugins/gas-analytics/helpers.d.ts +8 -5
  27. package/dist/src/internal/builtin-plugins/gas-analytics/helpers.d.ts.map +1 -1
  28. package/dist/src/internal/builtin-plugins/gas-analytics/helpers.js +20 -18
  29. package/dist/src/internal/builtin-plugins/gas-analytics/helpers.js.map +1 -1
  30. package/dist/src/internal/builtin-plugins/gas-analytics/hook-handlers/test.d.ts +7 -0
  31. package/dist/src/internal/builtin-plugins/gas-analytics/hook-handlers/test.d.ts.map +1 -0
  32. package/dist/src/internal/builtin-plugins/gas-analytics/hook-handlers/test.js +42 -0
  33. package/dist/src/internal/builtin-plugins/gas-analytics/hook-handlers/test.js.map +1 -0
  34. package/dist/src/internal/builtin-plugins/gas-analytics/index.d.ts.map +1 -1
  35. package/dist/src/internal/builtin-plugins/gas-analytics/index.js +36 -2
  36. package/dist/src/internal/builtin-plugins/gas-analytics/index.js.map +1 -1
  37. package/dist/src/internal/builtin-plugins/gas-analytics/snapshot-cheatcodes.d.ts +45 -0
  38. package/dist/src/internal/builtin-plugins/gas-analytics/snapshot-cheatcodes.d.ts.map +1 -0
  39. package/dist/src/internal/builtin-plugins/gas-analytics/snapshot-cheatcodes.js +276 -0
  40. package/dist/src/internal/builtin-plugins/gas-analytics/snapshot-cheatcodes.js.map +1 -0
  41. package/dist/src/internal/builtin-plugins/gas-analytics/tasks/solidity-test/task-action.d.ts +22 -0
  42. package/dist/src/internal/builtin-plugins/gas-analytics/tasks/solidity-test/task-action.d.ts.map +1 -0
  43. package/dist/src/internal/builtin-plugins/gas-analytics/tasks/solidity-test/task-action.js +88 -0
  44. package/dist/src/internal/builtin-plugins/gas-analytics/tasks/solidity-test/task-action.js.map +1 -0
  45. package/dist/src/internal/builtin-plugins/index.js +1 -1
  46. package/dist/src/internal/builtin-plugins/network-manager/edr/edr-provider.d.ts.map +1 -1
  47. package/dist/src/internal/builtin-plugins/network-manager/edr/edr-provider.js +17 -20
  48. package/dist/src/internal/builtin-plugins/network-manager/edr/edr-provider.js.map +1 -1
  49. package/dist/src/internal/builtin-plugins/network-manager/edr/stack-traces/stack-trace-generation-errors.d.ts +1 -1
  50. package/dist/src/internal/builtin-plugins/network-manager/edr/stack-traces/stack-trace-generation-errors.d.ts.map +1 -1
  51. package/dist/src/internal/builtin-plugins/network-manager/edr/stack-traces/stack-trace-generation-errors.js +2 -2
  52. package/dist/src/internal/builtin-plugins/network-manager/edr/stack-traces/stack-trace-generation-errors.js.map +1 -1
  53. package/dist/src/internal/builtin-plugins/network-manager/edr/type-validation.d.ts +0 -2
  54. package/dist/src/internal/builtin-plugins/network-manager/edr/type-validation.d.ts.map +1 -1
  55. package/dist/src/internal/builtin-plugins/network-manager/edr/type-validation.js +0 -6
  56. package/dist/src/internal/builtin-plugins/network-manager/edr/type-validation.js.map +1 -1
  57. package/dist/src/internal/builtin-plugins/network-manager/edr/utils/convert-to-edr.d.ts +1 -3
  58. package/dist/src/internal/builtin-plugins/network-manager/edr/utils/convert-to-edr.d.ts.map +1 -1
  59. package/dist/src/internal/builtin-plugins/network-manager/edr/utils/convert-to-edr.js +0 -49
  60. package/dist/src/internal/builtin-plugins/network-manager/edr/utils/convert-to-edr.js.map +1 -1
  61. package/dist/src/internal/builtin-plugins/solidity/build-results.d.ts +2 -2
  62. package/dist/src/internal/builtin-plugins/solidity/build-results.d.ts.map +1 -1
  63. package/dist/src/internal/builtin-plugins/solidity/build-results.js +2 -2
  64. package/dist/src/internal/builtin-plugins/solidity/build-results.js.map +1 -1
  65. package/dist/src/internal/builtin-plugins/solidity/build-system/build-system.d.ts +1 -0
  66. package/dist/src/internal/builtin-plugins/solidity/build-system/build-system.d.ts.map +1 -1
  67. package/dist/src/internal/builtin-plugins/solidity/build-system/build-system.js +13 -5
  68. package/dist/src/internal/builtin-plugins/solidity/build-system/build-system.js.map +1 -1
  69. package/dist/src/internal/builtin-plugins/solidity/build-system/compiler/compiler.js +1 -1
  70. package/dist/src/internal/builtin-plugins/solidity/build-system/compiler/compiler.js.map +1 -1
  71. package/dist/src/internal/builtin-plugins/solidity/build-system/dependency-graph.d.ts +1 -1
  72. package/dist/src/internal/builtin-plugins/solidity/build-system/dependency-graph.js +1 -1
  73. package/dist/src/internal/builtin-plugins/solidity/build-system/resolver/dependency-resolver.d.ts +2 -1
  74. package/dist/src/internal/builtin-plugins/solidity/build-system/resolver/dependency-resolver.d.ts.map +1 -1
  75. package/dist/src/internal/builtin-plugins/solidity/build-system/resolver/remapped-npm-packages-graph.d.ts +13 -1
  76. package/dist/src/internal/builtin-plugins/solidity/build-system/resolver/remapped-npm-packages-graph.d.ts.map +1 -1
  77. package/dist/src/internal/builtin-plugins/solidity/build-system/resolver/remapped-npm-packages-graph.js +30 -5
  78. package/dist/src/internal/builtin-plugins/solidity/build-system/resolver/remapped-npm-packages-graph.js.map +1 -1
  79. package/dist/src/internal/builtin-plugins/solidity/build-system/resolver/types.d.ts +3 -12
  80. package/dist/src/internal/builtin-plugins/solidity/build-system/resolver/types.d.ts.map +1 -1
  81. package/dist/src/internal/builtin-plugins/solidity/build-system/resolver/types.js.map +1 -1
  82. package/dist/src/internal/builtin-plugins/solidity/build-system/resolver/utils.d.ts +1 -1
  83. package/dist/src/internal/builtin-plugins/solidity/build-system/resolver/utils.d.ts.map +1 -1
  84. package/dist/src/internal/builtin-plugins/solidity/build-system/solc-config-selection.d.ts +8 -6
  85. package/dist/src/internal/builtin-plugins/solidity/build-system/solc-config-selection.d.ts.map +1 -1
  86. package/dist/src/internal/builtin-plugins/solidity/build-system/solc-config-selection.js +103 -27
  87. package/dist/src/internal/builtin-plugins/solidity/build-system/solc-config-selection.js.map +1 -1
  88. package/dist/src/internal/builtin-plugins/solidity/config.js +2 -2
  89. package/dist/src/internal/builtin-plugins/solidity/config.js.map +1 -1
  90. package/dist/src/internal/builtin-plugins/solidity/hook-handlers/hre.d.ts.map +1 -1
  91. package/dist/src/internal/builtin-plugins/solidity/hook-handlers/hre.js +5 -0
  92. package/dist/src/internal/builtin-plugins/solidity/hook-handlers/hre.js.map +1 -1
  93. package/dist/src/internal/builtin-plugins/solidity/tasks/build.js +1 -1
  94. package/dist/src/internal/builtin-plugins/solidity/tasks/build.js.map +1 -1
  95. package/dist/src/internal/builtin-plugins/solidity-test/config.d.ts +3 -1
  96. package/dist/src/internal/builtin-plugins/solidity-test/config.d.ts.map +1 -1
  97. package/dist/src/internal/builtin-plugins/solidity-test/config.js +9 -0
  98. package/dist/src/internal/builtin-plugins/solidity-test/config.js.map +1 -1
  99. package/dist/src/internal/builtin-plugins/solidity-test/edr-artifacts.d.ts +1 -1
  100. package/dist/src/internal/builtin-plugins/solidity-test/edr-artifacts.d.ts.map +1 -1
  101. package/dist/src/internal/builtin-plugins/solidity-test/edr-artifacts.js +1 -1
  102. package/dist/src/internal/builtin-plugins/solidity-test/edr-artifacts.js.map +1 -1
  103. package/dist/src/internal/builtin-plugins/solidity-test/helpers.d.ts.map +1 -1
  104. package/dist/src/internal/builtin-plugins/solidity-test/helpers.js +4 -10
  105. package/dist/src/internal/builtin-plugins/solidity-test/helpers.js.map +1 -1
  106. package/dist/src/internal/builtin-plugins/solidity-test/index.d.ts.map +1 -1
  107. package/dist/src/internal/builtin-plugins/solidity-test/index.js +0 -1
  108. package/dist/src/internal/builtin-plugins/solidity-test/index.js.map +1 -1
  109. package/dist/src/internal/builtin-plugins/solidity-test/runner.d.ts +2 -2
  110. package/dist/src/internal/builtin-plugins/solidity-test/runner.d.ts.map +1 -1
  111. package/dist/src/internal/builtin-plugins/solidity-test/runner.js +3 -3
  112. package/dist/src/internal/builtin-plugins/solidity-test/runner.js.map +1 -1
  113. package/dist/src/internal/builtin-plugins/solidity-test/task-action.d.ts +5 -0
  114. package/dist/src/internal/builtin-plugins/solidity-test/task-action.d.ts.map +1 -1
  115. package/dist/src/internal/builtin-plugins/solidity-test/task-action.js +21 -27
  116. package/dist/src/internal/builtin-plugins/solidity-test/task-action.js.map +1 -1
  117. package/dist/src/internal/builtin-plugins/solidity-test/type-extensions.d.ts +15 -10
  118. package/dist/src/internal/builtin-plugins/solidity-test/type-extensions.d.ts.map +1 -1
  119. package/dist/src/internal/builtin-plugins/telemetry/task-action.d.ts.map +1 -1
  120. package/dist/src/internal/builtin-plugins/telemetry/task-action.js +3 -2
  121. package/dist/src/internal/builtin-plugins/telemetry/task-action.js.map +1 -1
  122. package/dist/src/internal/builtin-plugins/test/task-action.d.ts.map +1 -1
  123. package/dist/src/internal/builtin-plugins/test/task-action.js +62 -13
  124. package/dist/src/internal/builtin-plugins/test/task-action.js.map +1 -1
  125. package/dist/src/internal/builtin-plugins/test/type-extensions.d.ts +27 -0
  126. package/dist/src/internal/builtin-plugins/test/type-extensions.d.ts.map +1 -1
  127. package/dist/src/internal/cli/banner-manager.d.ts +26 -0
  128. package/dist/src/internal/cli/banner-manager.d.ts.map +1 -0
  129. package/dist/src/internal/cli/banner-manager.js +146 -0
  130. package/dist/src/internal/cli/banner-manager.js.map +1 -0
  131. package/dist/src/internal/cli/init/init.d.ts.map +1 -1
  132. package/dist/src/internal/cli/init/init.js +8 -0
  133. package/dist/src/internal/cli/init/init.js.map +1 -1
  134. package/dist/src/internal/cli/main.d.ts.map +1 -1
  135. package/dist/src/internal/cli/main.js +18 -1
  136. package/dist/src/internal/cli/main.js.map +1 -1
  137. package/dist/src/internal/cli/telemetry/analytics/subprocess.js +2 -0
  138. package/dist/src/internal/cli/telemetry/analytics/subprocess.js.map +1 -1
  139. package/dist/src/internal/cli/telemetry/sentry/anonymize-paths.js +1 -1
  140. package/dist/src/internal/cli/telemetry/sentry/vendor/integrations/contextlines.d.ts +1 -1
  141. package/dist/src/internal/cli/telemetry/sentry/vendor/integrations/contextlines.d.ts.map +1 -1
  142. package/dist/src/internal/cli/telemetry/sentry/vendor/integrations/contextlines.js +47 -38
  143. package/dist/src/internal/cli/telemetry/sentry/vendor/integrations/contextlines.js.map +1 -1
  144. package/dist/src/internal/core/user-interruptions.js +1 -1
  145. package/dist/src/types/artifacts.d.ts +32 -3
  146. package/dist/src/types/artifacts.d.ts.map +1 -1
  147. package/dist/src/types/network.d.ts +1 -1
  148. package/dist/src/types/solidity/build-system.d.ts +56 -10
  149. package/dist/src/types/solidity/build-system.d.ts.map +1 -1
  150. package/dist/src/types/solidity/build-system.js +26 -2
  151. package/dist/src/types/solidity/build-system.js.map +1 -1
  152. package/dist/src/types/solidity/resolved-file.d.ts +2 -2
  153. package/dist/src/types/tasks.d.ts +8 -0
  154. package/dist/src/types/tasks.d.ts.map +1 -1
  155. package/dist/src/types/tasks.js.map +1 -1
  156. package/dist/src/types/test.d.ts +18 -0
  157. package/dist/src/types/test.d.ts.map +1 -1
  158. package/dist/src/types/utils.d.ts +16 -0
  159. package/dist/src/types/utils.d.ts.map +1 -1
  160. package/dist/src/utils/result.d.ts +33 -0
  161. package/dist/src/utils/result.d.ts.map +1 -0
  162. package/dist/src/utils/result.js +43 -0
  163. package/dist/src/utils/result.js.map +1 -0
  164. package/package.json +6 -5
  165. package/src/internal/builtin-plugins/artifacts/artifact-manager.ts +4 -1
  166. package/src/internal/builtin-plugins/artifacts/hook-handlers/hre.ts +4 -1
  167. package/src/internal/builtin-plugins/coverage/helpers.ts +11 -29
  168. package/src/internal/builtin-plugins/coverage/hook-handlers/test.ts +68 -0
  169. package/src/internal/builtin-plugins/coverage/index.ts +1 -0
  170. package/src/internal/builtin-plugins/gas-analytics/function-gas-snapshots.ts +473 -0
  171. package/src/internal/builtin-plugins/gas-analytics/gas-analytics-manager.ts +3 -17
  172. package/src/internal/builtin-plugins/gas-analytics/helpers.ts +38 -27
  173. package/src/internal/builtin-plugins/gas-analytics/hook-handlers/test.ts +68 -0
  174. package/src/internal/builtin-plugins/gas-analytics/index.ts +37 -2
  175. package/src/internal/builtin-plugins/gas-analytics/snapshot-cheatcodes.ts +454 -0
  176. package/src/internal/builtin-plugins/gas-analytics/tasks/solidity-test/task-action.ts +172 -0
  177. package/src/internal/builtin-plugins/index.ts +1 -1
  178. package/src/internal/builtin-plugins/network-manager/edr/edr-provider.ts +21 -28
  179. package/src/internal/builtin-plugins/network-manager/edr/stack-traces/stack-trace-generation-errors.ts +5 -2
  180. package/src/internal/builtin-plugins/network-manager/edr/type-validation.ts +0 -13
  181. package/src/internal/builtin-plugins/network-manager/edr/utils/convert-to-edr.ts +0 -64
  182. package/src/internal/builtin-plugins/solidity/build-results.ts +3 -1
  183. package/src/internal/builtin-plugins/solidity/build-system/build-system.ts +15 -5
  184. package/src/internal/builtin-plugins/solidity/build-system/compiler/compiler.ts +1 -1
  185. package/src/internal/builtin-plugins/solidity/build-system/dependency-graph.ts +1 -1
  186. package/src/internal/builtin-plugins/solidity/build-system/resolver/dependency-resolver.ts +1 -1
  187. package/src/internal/builtin-plugins/solidity/build-system/resolver/remapped-npm-packages-graph.ts +36 -6
  188. package/src/internal/builtin-plugins/solidity/build-system/resolver/types.ts +3 -9
  189. package/src/internal/builtin-plugins/solidity/build-system/resolver/utils.ts +1 -1
  190. package/src/internal/builtin-plugins/solidity/build-system/solc-config-selection.ts +125 -28
  191. package/src/internal/builtin-plugins/solidity/config.ts +2 -2
  192. package/src/internal/builtin-plugins/solidity/hook-handlers/hre.ts +8 -0
  193. package/src/internal/builtin-plugins/solidity/tasks/build.ts +1 -1
  194. package/src/internal/builtin-plugins/solidity-test/config.ts +15 -0
  195. package/src/internal/builtin-plugins/solidity-test/edr-artifacts.ts +2 -2
  196. package/src/internal/builtin-plugins/solidity-test/helpers.ts +6 -14
  197. package/src/internal/builtin-plugins/solidity-test/index.ts +0 -1
  198. package/src/internal/builtin-plugins/solidity-test/runner.ts +3 -3
  199. package/src/internal/builtin-plugins/solidity-test/task-action.ts +47 -40
  200. package/src/internal/builtin-plugins/solidity-test/type-extensions.ts +17 -10
  201. package/src/internal/builtin-plugins/telemetry/task-action.ts +4 -2
  202. package/src/internal/builtin-plugins/test/task-action.ts +88 -24
  203. package/src/internal/builtin-plugins/test/type-extensions.ts +42 -0
  204. package/src/internal/cli/banner-manager.ts +234 -0
  205. package/src/internal/cli/init/init.ts +8 -0
  206. package/src/internal/cli/main.ts +19 -1
  207. package/src/internal/cli/telemetry/analytics/subprocess.ts +2 -0
  208. package/src/internal/cli/telemetry/sentry/anonymize-paths.ts +1 -1
  209. package/src/internal/cli/telemetry/sentry/vendor/integrations/contextlines.ts +98 -50
  210. package/src/internal/core/user-interruptions.ts +1 -1
  211. package/src/types/artifacts.ts +40 -3
  212. package/src/types/hre.ts +1 -1
  213. package/src/types/network.ts +1 -1
  214. package/src/types/solidity/build-system.ts +75 -14
  215. package/src/types/solidity/resolved-file.ts +2 -2
  216. package/src/types/tasks.ts +10 -0
  217. package/src/types/test.ts +20 -0
  218. package/src/types/utils.ts +14 -0
  219. package/src/utils/result.ts +57 -0
  220. package/templates/hardhat-3/01-node-test-runner-viem/package.json +9 -9
  221. package/templates/hardhat-3/02-mocha-ethers/package.json +10 -10
  222. package/templates/hardhat-3/03-minimal/package.json +1 -1
  223. package/dist/src/internal/builtin-plugins/network-manager/edr/types/output.d.ts +0 -19
  224. package/dist/src/internal/builtin-plugins/network-manager/edr/types/output.d.ts.map +0 -1
  225. package/dist/src/internal/builtin-plugins/network-manager/edr/types/output.js +0 -2
  226. package/dist/src/internal/builtin-plugins/network-manager/edr/types/output.js.map +0 -1
  227. package/src/internal/builtin-plugins/network-manager/edr/types/output.ts +0 -19
@@ -4,13 +4,21 @@ import type {
4
4
  Task,
5
5
  TaskArguments,
6
6
  } from "../../../types/tasks.js";
7
+ import type { TestSummary } from "../../../types/test.js";
8
+ import type { Result } from "../../../types/utils.js";
7
9
 
8
10
  import {
9
11
  assertHardhatInvariant,
10
12
  HardhatError,
11
13
  } from "@nomicfoundation/hardhat-errors";
14
+ import { isObject } from "@nomicfoundation/hardhat-utils/lang";
12
15
  import chalk, { type ChalkInstance } from "chalk";
13
16
 
17
+ import {
18
+ errorResult,
19
+ isResult,
20
+ successfulResult,
21
+ } from "../../../utils/result.js";
14
22
  import { HardhatRuntimeEnvironmentImplementation } from "../../core/hre.js";
15
23
 
16
24
  interface TestActionArguments {
@@ -21,10 +29,33 @@ interface TestActionArguments {
21
29
  verbosity: number;
22
30
  }
23
31
 
32
+ // Old plugins may only return { failed, passed } without skipped/todo,
33
+ // so we accept a partial shape and fill defaults in the coordinator.
34
+ interface PartialTestSummary extends Omit<TestSummary, "skipped" | "todo"> {
35
+ skipped?: number;
36
+ todo?: number;
37
+ }
38
+
39
+ function isTestSummary(value: unknown): value is PartialTestSummary {
40
+ return (
41
+ isObject(value) &&
42
+ typeof value.failed === "number" &&
43
+ typeof value.passed === "number" &&
44
+ (value.skipped === undefined || typeof value.skipped === "number") &&
45
+ (value.todo === undefined || typeof value.todo === "number")
46
+ );
47
+ }
48
+
49
+ function isTestRunResult(
50
+ value: unknown,
51
+ ): value is { summary: PartialTestSummary } {
52
+ return isObject(value) && "summary" in value && isTestSummary(value.summary);
53
+ }
54
+
24
55
  const runAllTests: NewTaskActionFunction<TestActionArguments> = async (
25
- { testFiles, chainType, grep, noCompile, verbosity },
56
+ { testFiles, chainType, grep, noCompile, verbosity, ...otherArgs },
26
57
  hre,
27
- ) => {
58
+ ): Promise<Result<void, void>> => {
28
59
  // If this code is executed, it means the user has not specified a test runner.
29
60
  // If file paths are specified, we need to determine which test runner applies to each test file.
30
61
  // If no file paths are specified, each test runner will execute all tests located under its configured path in the Hardhat configuration.
@@ -49,18 +80,10 @@ const runAllTests: NewTaskActionFunction<TestActionArguments> = async (
49
80
  hre._coverage.disableReport();
50
81
  }
51
82
 
52
- const testSummaries: Record<
53
- string,
54
- {
55
- failed?: number;
56
- passed?: number;
57
- skipped?: number;
58
- todo?: number;
59
- failureOutput?: string;
60
- }
61
- > = {};
83
+ const testSummaries: Record<string, TestSummary> = {};
62
84
 
63
85
  let failureIndex = 1;
86
+ let hasFailures = false;
64
87
  for (const subtask of thisTask.subtasks.values()) {
65
88
  const files = getTestFilesForSubtask(subtask, testFiles, subtasksToFiles);
66
89
 
@@ -84,18 +107,57 @@ const runAllTests: NewTaskActionFunction<TestActionArguments> = async (
84
107
  args.verbosity = verbosity;
85
108
  }
86
109
 
87
- const summaryId = subtask.id[subtask.id.length - 1];
110
+ for (const [key, value] of Object.entries(otherArgs)) {
111
+ if (subtask.options.has(key)) {
112
+ args[key] = value;
113
+ }
114
+ }
88
115
 
89
116
  if (subtask.options.has("testSummaryIndex")) {
90
117
  args.testSummaryIndex = failureIndex;
118
+ }
91
119
 
92
- testSummaries[summaryId] = await subtask.run(args);
93
- failureIndex += testSummaries[summaryId].failed ?? 0;
94
- } else if (summaryId === "mocha") {
95
- // mocha doesn't use the testSummaryIndex, but it does return failure & success counts
96
- testSummaries[summaryId] = await subtask.run(args);
120
+ const subtaskResult = await subtask.run(args);
121
+
122
+ let summary: PartialTestSummary | undefined;
123
+ let subtaskFailed = false;
124
+
125
+ if (isResult(subtaskResult, isTestRunResult, isTestRunResult)) {
126
+ const testRunResult = subtaskResult.success
127
+ ? subtaskResult.value
128
+ : subtaskResult.error;
129
+ summary = testRunResult.summary;
130
+ subtaskFailed = !subtaskResult.success;
131
+ } else if (isResult(subtaskResult, isTestSummary, isTestSummary)) {
132
+ // Support plugins that return Result<TestSummary, TestSummary>
133
+ summary = subtaskResult.success
134
+ ? subtaskResult.value
135
+ : subtaskResult.error;
136
+ subtaskFailed = !subtaskResult.success;
137
+ } else if (isTestSummary(subtaskResult)) {
138
+ // Support plugins that return TestSummary directly
139
+ summary = subtaskResult;
140
+ subtaskFailed = process.exitCode !== undefined && process.exitCode !== 0;
97
141
  } else {
98
- await subtask.run(args);
142
+ // Fallback for plugins that don't return a summary at all
143
+ subtaskFailed = process.exitCode !== undefined && process.exitCode !== 0;
144
+ }
145
+
146
+ if (summary !== undefined) {
147
+ const summaryId = subtask.id[subtask.id.length - 1];
148
+ testSummaries[summaryId] = {
149
+ skipped: 0,
150
+ todo: 0,
151
+ ...summary,
152
+ };
153
+
154
+ if (subtask.options.has("testSummaryIndex")) {
155
+ failureIndex += summary.failed;
156
+ }
157
+ }
158
+
159
+ if (subtaskFailed) {
160
+ hasFailures = true;
99
161
  }
100
162
  }
101
163
 
@@ -106,19 +168,19 @@ const runAllTests: NewTaskActionFunction<TestActionArguments> = async (
106
168
  const outputLines: string[] = [];
107
169
 
108
170
  for (const [subtaskName, results] of Object.entries(testSummaries)) {
109
- if (results.passed !== undefined && results.passed > 0) {
171
+ if (results.passed > 0) {
110
172
  passed.push([subtaskName, results.passed]);
111
173
  }
112
174
 
113
- if (results.failed !== undefined && results.failed > 0) {
175
+ if (results.failed > 0) {
114
176
  failed.push([subtaskName, results.failed]);
115
177
  }
116
178
 
117
- if (results.skipped !== undefined && results.skipped > 0) {
179
+ if (results.skipped > 0) {
118
180
  skipped.push([subtaskName, results.skipped]);
119
181
  }
120
182
 
121
- if (results.todo !== undefined && results.todo > 0) {
183
+ if (results.todo > 0) {
122
184
  todo.push([subtaskName, results.todo]);
123
185
  }
124
186
 
@@ -176,9 +238,11 @@ const runAllTests: NewTaskActionFunction<TestActionArguments> = async (
176
238
  console.log();
177
239
  }
178
240
 
179
- if (process.exitCode !== undefined && process.exitCode !== 0) {
241
+ if (hasFailures) {
180
242
  console.error("Test run failed");
181
243
  }
244
+
245
+ return hasFailures ? errorResult() : successfulResult();
182
246
  };
183
247
 
184
248
  function logSummaryLine(
@@ -39,5 +39,47 @@ declare module "../../../types/hooks.js" {
39
39
  filePath: string,
40
40
  ) => Promise<string | undefined>,
41
41
  ) => Promise<string | undefined>;
42
+
43
+ /**
44
+ * This hook is triggered at the start of a test run, before tests execute.
45
+ *
46
+ * @param context The hook context.
47
+ * @param id A string identifier for the test runner (e.g., "solidity", "nodejs", "mocha").
48
+ * @param next A function to call the next handler for this hook, or the
49
+ * default implementation if no more handlers exist.
50
+ */
51
+ onTestRunStart: (
52
+ context: HookContext,
53
+ id: string,
54
+ next: (nextContext: HookContext, id: string) => Promise<void>,
55
+ ) => Promise<void>;
56
+
57
+ /**
58
+ * This hook is triggered when a test worker has finished executing.
59
+ *
60
+ * @param context The hook context.
61
+ * @param id A string identifier for the test runner (e.g., "solidity", "nodejs", "mocha").
62
+ * @param next A function to call the next handler for this hook, or the
63
+ * default implementation if no more handlers exist.
64
+ */
65
+ onTestWorkerDone: (
66
+ context: HookContext,
67
+ id: string,
68
+ next: (nextContext: HookContext, id: string) => Promise<void>,
69
+ ) => Promise<void>;
70
+
71
+ /**
72
+ * This hook is triggered at the end of a test run, after all tests have completed.
73
+ *
74
+ * @param context The hook context.
75
+ * @param id A string identifier for the test runner (e.g., "solidity", "nodejs", "mocha").
76
+ * @param next A function to call the next handler for this hook, or the
77
+ * default implementation if no more handlers exist.
78
+ */
79
+ onTestRunDone: (
80
+ context: HookContext,
81
+ id: string,
82
+ next: (nextContext: HookContext, id: string) => Promise<void>,
83
+ ) => Promise<void>;
42
84
  }
43
85
  }
@@ -0,0 +1,234 @@
1
+ import type { Dispatcher } from "@nomicfoundation/hardhat-utils/request";
2
+
3
+ import path from "node:path";
4
+
5
+ import {
6
+ readJsonFile,
7
+ writeJsonFile,
8
+ FileNotFoundError,
9
+ } from "@nomicfoundation/hardhat-utils/fs";
10
+ import { getCacheDir } from "@nomicfoundation/hardhat-utils/global-dir";
11
+ import { isObject } from "@nomicfoundation/hardhat-utils/lang";
12
+ import { getRequest } from "@nomicfoundation/hardhat-utils/request";
13
+ import debug from "debug";
14
+
15
+ const log = debug("hardhat:util:banner-manager");
16
+
17
+ interface BannerConfig {
18
+ enabled: boolean;
19
+ formattedMessages: string[];
20
+ minSecondsBetweenDisplays: number;
21
+ minSecondsBetweenRequests: number;
22
+ }
23
+
24
+ export const BANNER_CONFIG_URL =
25
+ "https://raw.githubusercontent.com/NomicFoundation/hardhat/refs/heads/main/banner-config-v3.json";
26
+
27
+ export const BANNER_CACHE_FILE_NAME = "banner-config-v3.json";
28
+
29
+ export class BannerManager {
30
+ static #instance: BannerManager | undefined;
31
+
32
+ #bannerConfig: BannerConfig | undefined;
33
+ #lastDisplayTime: number;
34
+ #lastRequestTime: number;
35
+ readonly #dispatcher: Dispatcher | undefined;
36
+ readonly #print: (message: string) => void;
37
+
38
+ private constructor(
39
+ bannerConfig: BannerConfig | undefined,
40
+ lastDisplayTime: number,
41
+ lastRequestTime: number,
42
+ dispatcher: Dispatcher | undefined,
43
+ print: (message: string) => void,
44
+ ) {
45
+ this.#bannerConfig = bannerConfig;
46
+ this.#lastDisplayTime = lastDisplayTime;
47
+ this.#lastRequestTime = lastRequestTime;
48
+ this.#dispatcher = dispatcher;
49
+ this.#print = print;
50
+ }
51
+
52
+ /**
53
+ * Returns a global instance of BannerManager.
54
+ *
55
+ * @param options Options used for testing purposes. Only used in the first
56
+ * invocation of this function, or after calling `resetInstance`.
57
+ * @returns The current global instance of BannerManager.
58
+ */
59
+ public static async getInstance(options?: {
60
+ testDispatcher?: Dispatcher;
61
+ print?: (message: string) => void;
62
+ }): Promise<BannerManager> {
63
+ if (this.#instance === undefined) {
64
+ log("Initializing BannerManager");
65
+ const { bannerConfig, lastDisplayTime, lastRequestTime } =
66
+ await readCache();
67
+ this.#instance = new BannerManager(
68
+ bannerConfig,
69
+ lastDisplayTime,
70
+ lastRequestTime,
71
+ options?.testDispatcher,
72
+ options?.print ?? console.log,
73
+ );
74
+ }
75
+
76
+ return this.#instance;
77
+ }
78
+
79
+ public static resetInstance(): void {
80
+ this.#instance = undefined;
81
+ }
82
+
83
+ /**
84
+ * Displays a banner message, if any.
85
+ *
86
+ * @param timeout The timeout in milliseconds to wait for the banner display.
87
+ */
88
+ public async showBanner(timeout?: number): Promise<void> {
89
+ await this.#requestBannerConfig(timeout);
90
+
91
+ if (
92
+ this.#bannerConfig === undefined ||
93
+ !this.#bannerConfig.enabled ||
94
+ this.#bannerConfig.formattedMessages.length === 0
95
+ ) {
96
+ log("Banner is disabled or no messages available.");
97
+ return;
98
+ }
99
+
100
+ const { formattedMessages, minSecondsBetweenDisplays } = this.#bannerConfig;
101
+
102
+ const timeSinceLastDisplay = Date.now() - this.#lastDisplayTime;
103
+ if (timeSinceLastDisplay < minSecondsBetweenDisplays * 1000) {
104
+ log(
105
+ `Skipping banner display. Time since last display: ${timeSinceLastDisplay}ms`,
106
+ );
107
+ return;
108
+ }
109
+
110
+ // select a random message from the formattedMessages array
111
+ const randomIndex = Math.floor(Math.random() * formattedMessages.length);
112
+ const message = formattedMessages[randomIndex];
113
+
114
+ this.#print(message);
115
+ this.#lastDisplayTime = Date.now();
116
+ await writeCache({
117
+ bannerConfig: this.#bannerConfig,
118
+ lastDisplayTime: this.#lastDisplayTime,
119
+ lastRequestTime: this.#lastRequestTime,
120
+ });
121
+ }
122
+
123
+ async #requestBannerConfig(timeout?: number): Promise<void> {
124
+ if (this.#bannerConfig !== undefined) {
125
+ const timeSinceLastRequest = Date.now() - this.#lastRequestTime;
126
+ if (
127
+ timeSinceLastRequest <
128
+ this.#bannerConfig.minSecondsBetweenRequests * 1000
129
+ ) {
130
+ log(
131
+ `Skipping banner config request. Time since last request: ${timeSinceLastRequest}ms`,
132
+ );
133
+ return;
134
+ }
135
+ }
136
+
137
+ try {
138
+ const response = await getRequest(
139
+ BANNER_CONFIG_URL,
140
+ undefined,
141
+ this.#dispatcher ?? { timeout },
142
+ );
143
+
144
+ const bannerConfig: unknown = await response.body.json();
145
+
146
+ if (!this.#isBannerConfig(bannerConfig)) {
147
+ log(`Invalid banner config received:`, bannerConfig);
148
+ return;
149
+ }
150
+
151
+ this.#bannerConfig = bannerConfig;
152
+ this.#lastRequestTime = Date.now();
153
+
154
+ await writeCache({
155
+ bannerConfig: this.#bannerConfig,
156
+ lastDisplayTime: this.#lastDisplayTime,
157
+ lastRequestTime: this.#lastRequestTime,
158
+ });
159
+ } catch (error) {
160
+ log(
161
+ `Error requesting banner config: ${
162
+ error instanceof Error ? error.message : JSON.stringify(error)
163
+ }`,
164
+ );
165
+ }
166
+ }
167
+
168
+ #isBannerConfig(value: unknown): value is BannerConfig {
169
+ if (!isObject(value)) {
170
+ return false;
171
+ }
172
+
173
+ return (
174
+ Object.getOwnPropertyNames(value).length === 4 &&
175
+ "enabled" in value &&
176
+ typeof value.enabled === "boolean" &&
177
+ "formattedMessages" in value &&
178
+ Array.isArray(value.formattedMessages) &&
179
+ value.formattedMessages.every(
180
+ (message: unknown) => typeof message === "string",
181
+ ) &&
182
+ "minSecondsBetweenDisplays" in value &&
183
+ typeof value.minSecondsBetweenDisplays === "number" &&
184
+ "minSecondsBetweenRequests" in value &&
185
+ typeof value.minSecondsBetweenRequests === "number"
186
+ );
187
+ }
188
+ }
189
+
190
+ interface BannerCache {
191
+ bannerConfig: BannerConfig | undefined;
192
+ lastDisplayTime: number;
193
+ lastRequestTime: number;
194
+ }
195
+
196
+ async function readCache(): Promise<BannerCache> {
197
+ const cacheDir = await getCacheDir();
198
+ const bannerCacheFilePath = path.join(cacheDir, BANNER_CACHE_FILE_NAME);
199
+
200
+ try {
201
+ return await readJsonFile<BannerCache>(bannerCacheFilePath);
202
+ } catch (error) {
203
+ if (error instanceof FileNotFoundError) {
204
+ log("No banner cache file found, using defaults");
205
+ } else {
206
+ log(
207
+ `Error reading cache file: ${
208
+ error instanceof Error ? error.message : JSON.stringify(error)
209
+ }`,
210
+ );
211
+ }
212
+
213
+ return {
214
+ bannerConfig: undefined,
215
+ lastDisplayTime: 0,
216
+ lastRequestTime: 0,
217
+ };
218
+ }
219
+ }
220
+
221
+ async function writeCache(cache: BannerCache): Promise<void> {
222
+ const cacheDir = await getCacheDir();
223
+ const bannerCacheFilePath = path.join(cacheDir, BANNER_CACHE_FILE_NAME);
224
+
225
+ try {
226
+ await writeJsonFile(bannerCacheFilePath, cache);
227
+ } catch (error) {
228
+ log(
229
+ `Error writing cache file: ${
230
+ error instanceof Error ? error.message : JSON.stringify(error)
231
+ }`,
232
+ );
233
+ }
234
+ }
@@ -131,6 +131,14 @@ export async function initHardhat(options?: InitHardhatOptions): Promise<void> {
131
131
  ]);
132
132
 
133
133
  showStarOnGitHubMessage();
134
+
135
+ try {
136
+ const { BannerManager } = await import("../banner-manager.js");
137
+ const bannerManager = await BannerManager.getInstance();
138
+ await bannerManager.showBanner();
139
+ } catch (bannerError) {
140
+ log("Error showing banner", bannerError);
141
+ }
134
142
  } catch (e) {
135
143
  if (e === "") {
136
144
  // If the user cancels any prompt, we quit silently
@@ -28,6 +28,7 @@ import {
28
28
  type OptionDefinition,
29
29
  type PositionalArgumentDefinition,
30
30
  } from "../../types/arguments.js";
31
+ import { isResult } from "../../utils/result.js";
31
32
  import { BUILTIN_GLOBAL_OPTIONS_DEFINITIONS } from "../builtin-global-options.js";
32
33
  import { builtinPlugins } from "../builtin-plugins/index.js";
33
34
  import {
@@ -217,7 +218,24 @@ export async function main(
217
218
 
218
219
  log(`Running task "${task.id.join(" ")}"`);
219
220
 
220
- await Promise.all([task.run(taskArguments), sendTaskAnalytics(task.id)]);
221
+ const [taskResult] = await Promise.all([
222
+ task.run(taskArguments),
223
+ sendTaskAnalytics(task.id),
224
+ ]);
225
+
226
+ if (isResult(taskResult) && !taskResult.success) {
227
+ process.exitCode = 1;
228
+ }
229
+
230
+ if (!isCi() && process.stdout.isTTY === true) {
231
+ try {
232
+ const { BannerManager } = await import("./banner-manager.js");
233
+ const bannerManager = await BannerManager.getInstance();
234
+ await bannerManager.showBanner(200);
235
+ } catch (bannerError) {
236
+ log("Error showing banner", bannerError);
237
+ }
238
+ }
221
239
  } catch (error) {
222
240
  ensureError(error);
223
241
  printErrorMessages(error, builtinGlobalOptions?.showStackTraces);
@@ -5,7 +5,9 @@ import { postJsonRequest } from "@nomicfoundation/hardhat-utils/request";
5
5
 
6
6
  // These keys are expected to be public
7
7
  const ANALYTICS_URL = "https://www.google-analytics.com/mp/collect";
8
+ /* cspell:disable-next-line */
8
9
  // const API_SECRET = "iXzTRik5RhahYpgiatSv1w"; // DEV
10
+ /* cspell:disable-next-line */
9
11
  // const MEASUREMENT_ID = "G-ZFZWHGZ64H"; // DEV
10
12
  const API_SECRET = "fQ5joCsDRTOp55wX8a2cVw"; // PROD
11
13
  const MEASUREMENT_ID = "G-8LQ007N2QJ"; // PROD
@@ -107,7 +107,7 @@ function anonymizeSinglePath(path: string): string {
107
107
  return normalizedPath;
108
108
  }
109
109
 
110
- // We first get the index of the first /node_modules to desambiguate some
110
+ // We first get the index of the first /node_modules to disambiguate some
111
111
  // special cases below
112
112
  const nodeModulesIndex = normalizedPath.indexOf("/node_modules");
113
113