hardhat 3.5.0 → 3.6.0

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 (82) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/dist/src/internal/builtin-plugins/flatten/task-action.d.ts.map +1 -1
  3. package/dist/src/internal/builtin-plugins/flatten/task-action.js +23 -7
  4. package/dist/src/internal/builtin-plugins/flatten/task-action.js.map +1 -1
  5. package/dist/src/internal/builtin-plugins/solidity/build-system/build-system.d.ts +2 -5
  6. package/dist/src/internal/builtin-plugins/solidity/build-system/build-system.d.ts.map +1 -1
  7. package/dist/src/internal/builtin-plugins/solidity/build-system/build-system.js +79 -12
  8. package/dist/src/internal/builtin-plugins/solidity/build-system/build-system.js.map +1 -1
  9. package/dist/src/internal/builtin-plugins/solidity/build-system/warning-suppression.d.ts +6 -10
  10. package/dist/src/internal/builtin-plugins/solidity/build-system/warning-suppression.d.ts.map +1 -1
  11. package/dist/src/internal/builtin-plugins/solidity/build-system/warning-suppression.js +54 -43
  12. package/dist/src/internal/builtin-plugins/solidity/build-system/warning-suppression.js.map +1 -1
  13. package/dist/src/internal/builtin-plugins/solidity/hook-handlers/hre.d.ts.map +1 -1
  14. package/dist/src/internal/builtin-plugins/solidity/hook-handlers/hre.js +2 -1
  15. package/dist/src/internal/builtin-plugins/solidity/hook-handlers/hre.js.map +1 -1
  16. package/dist/src/internal/builtin-plugins/solidity/tasks/build.d.ts.map +1 -1
  17. package/dist/src/internal/builtin-plugins/solidity/tasks/build.js +1 -5
  18. package/dist/src/internal/builtin-plugins/solidity/tasks/build.js.map +1 -1
  19. package/dist/src/internal/builtin-plugins/solidity/type-extensions.d.ts +53 -1
  20. package/dist/src/internal/builtin-plugins/solidity/type-extensions.d.ts.map +1 -1
  21. package/dist/src/internal/cli/error-handler.d.ts +11 -2
  22. package/dist/src/internal/cli/error-handler.d.ts.map +1 -1
  23. package/dist/src/internal/cli/error-handler.js +72 -46
  24. package/dist/src/internal/cli/error-handler.js.map +1 -1
  25. package/dist/src/internal/cli/help/utils.d.ts.map +1 -1
  26. package/dist/src/internal/cli/help/utils.js +3 -2
  27. package/dist/src/internal/cli/help/utils.js.map +1 -1
  28. package/dist/src/internal/cli/init/init.d.ts.map +1 -1
  29. package/dist/src/internal/cli/init/init.js +115 -4
  30. package/dist/src/internal/cli/init/init.js.map +1 -1
  31. package/dist/src/internal/cli/init/package-manager.d.ts +7 -1
  32. package/dist/src/internal/cli/init/package-manager.d.ts.map +1 -1
  33. package/dist/src/internal/cli/init/package-manager.js +14 -2
  34. package/dist/src/internal/cli/init/package-manager.js.map +1 -1
  35. package/dist/src/internal/cli/main.d.ts.map +1 -1
  36. package/dist/src/internal/cli/main.js +4 -2
  37. package/dist/src/internal/cli/main.js.map +1 -1
  38. package/dist/src/internal/cli/telemetry/error-classification/classifier.d.ts +2 -1
  39. package/dist/src/internal/cli/telemetry/error-classification/classifier.d.ts.map +1 -1
  40. package/dist/src/internal/cli/telemetry/error-classification/classifier.js +2 -1
  41. package/dist/src/internal/cli/telemetry/error-classification/classifier.js.map +1 -1
  42. package/dist/src/internal/core/tasks/resolved-task.d.ts.map +1 -1
  43. package/dist/src/internal/core/tasks/resolved-task.js +3 -2
  44. package/dist/src/internal/core/tasks/resolved-task.js.map +1 -1
  45. package/dist/src/internal/core/tasks/utils.d.ts +3 -0
  46. package/dist/src/internal/core/tasks/utils.d.ts.map +1 -1
  47. package/dist/src/internal/core/tasks/utils.js +6 -0
  48. package/dist/src/internal/core/tasks/utils.js.map +1 -1
  49. package/dist/src/internal/core/tasks/validations.js +3 -3
  50. package/dist/src/internal/core/tasks/validations.js.map +1 -1
  51. package/dist/src/internal/using-hardhat2-plugin-errors.d.ts.map +1 -1
  52. package/dist/src/internal/using-hardhat2-plugin-errors.js +8 -11
  53. package/dist/src/internal/using-hardhat2-plugin-errors.js.map +1 -1
  54. package/dist/src/types/solidity/build-system.d.ts +22 -2
  55. package/dist/src/types/solidity/build-system.d.ts.map +1 -1
  56. package/dist/src/types/solidity/build-system.js.map +1 -1
  57. package/package.json +4 -3
  58. package/skills/hardhat/SKILL.md +152 -0
  59. package/skills/hardhat-toolbox-mocha-ethers/SKILL.md +119 -0
  60. package/skills/hardhat-toolbox-viem/SKILL.md +126 -0
  61. package/src/internal/builtin-plugins/flatten/task-action.ts +37 -7
  62. package/src/internal/builtin-plugins/solidity/build-system/build-system.ts +126 -21
  63. package/src/internal/builtin-plugins/solidity/build-system/warning-suppression.ts +81 -59
  64. package/src/internal/builtin-plugins/solidity/hook-handlers/hre.ts +3 -2
  65. package/src/internal/builtin-plugins/solidity/tasks/build.ts +1 -6
  66. package/src/internal/builtin-plugins/solidity/type-extensions.ts +69 -0
  67. package/src/internal/cli/error-handler.ts +103 -72
  68. package/src/internal/cli/help/utils.ts +9 -2
  69. package/src/internal/cli/init/init.ts +141 -0
  70. package/src/internal/cli/init/package-manager.ts +18 -1
  71. package/src/internal/cli/main.ts +5 -3
  72. package/src/internal/cli/telemetry/error-classification/classifier.ts +2 -1
  73. package/src/internal/core/tasks/resolved-task.ts +5 -2
  74. package/src/internal/core/tasks/utils.ts +13 -0
  75. package/src/internal/core/tasks/validations.ts +3 -3
  76. package/src/internal/using-hardhat2-plugin-errors.ts +8 -11
  77. package/src/types/solidity/build-system.ts +24 -5
  78. package/templates/hardhat-3/01-node-test-runner-viem/AGENTS.md +20 -0
  79. package/templates/hardhat-3/01-node-test-runner-viem/package.json +3 -3
  80. package/templates/hardhat-3/02-mocha-ethers/AGENTS.md +20 -0
  81. package/templates/hardhat-3/02-mocha-ethers/package.json +3 -3
  82. package/templates/hardhat-3/03-minimal/package.json +1 -1
@@ -5,10 +5,13 @@ import type {
5
5
  FileBuildResult,
6
6
  SolidityBuildSystem,
7
7
  } from "../../../types/solidity/build-system.js";
8
+ import type { CompilationJob } from "../../../types/solidity/compilation-job.js";
9
+ import type { CompilerOutputError } from "../../../types/solidity/compiler-io.js";
8
10
  import type {
9
11
  Compiler,
10
12
  CompilerInput,
11
13
  CompilerOutput,
14
+ ResolvedBuildOptions,
12
15
  } from "../../../types/solidity.js";
13
16
 
14
17
  declare module "../../../types/config.js" {
@@ -359,6 +362,7 @@ declare module "../../../types/hooks.js" {
359
362
  * @param compilerConfig The compiler configuration to get a compiler for.
360
363
  * @param next A function to call the next handler for this hook.
361
364
  * @returns A Compiler instance.
365
+ * @deprecated This hook will soon be removed.
362
366
  */
363
367
  getCompiler: (
364
368
  context: HookContext,
@@ -377,6 +381,8 @@ declare module "../../../types/hooks.js" {
377
381
  * @param artifactPaths The file paths of artifacts that remain after cleanup.
378
382
  * @param next A function to call the next handler for this hook, or the
379
383
  * default implementation if no more handlers exist.
384
+ * @deprecated This hook will soon be removed. Use
385
+ * `processArtifactsAfterSuccessfulBuild` instead.
380
386
  */
381
387
  onCleanUpArtifacts: (
382
388
  context: HookContext,
@@ -402,6 +408,7 @@ declare module "../../../types/hooks.js" {
402
408
  * default implementation if no more handlers exist.
403
409
  *
404
410
  * @returns The modified file content.
411
+ * @deprecated This hook will soon be removed.
405
412
  */
406
413
  preprocessProjectFileBeforeBuilding(
407
414
  context: HookContext,
@@ -427,6 +434,7 @@ declare module "../../../types/hooks.js" {
427
434
  * default implementation if no more handlers exist.
428
435
  *
429
436
  * @returns The modified solc input.
437
+ * @deprecated This hook will soon be removed.
430
438
  */
431
439
  preprocessSolcInputBeforeBuilding(
432
440
  context: HookContext,
@@ -437,6 +445,9 @@ declare module "../../../types/hooks.js" {
437
445
  ) => Promise<CompilerInput>,
438
446
  ): Promise<CompilerInput>;
439
447
 
448
+ /**
449
+ * @deprecated This hook will soon be removed.
450
+ */
440
451
  readSourceFile: (
441
452
  context: HookContext,
442
453
  absolutePath: string,
@@ -482,6 +493,7 @@ declare module "../../../types/hooks.js" {
482
493
  * @param solcConfig The compiler configuration (version, type, etc.).
483
494
  * @param next A function to call the next handler for this hook, or the
484
495
  * default implementation if no more handlers exist.
496
+ * @deprecated This hook will soon be removed. Use `getCompilationJobErrors` instead.
485
497
  */
486
498
  invokeSolc(
487
499
  context: HookContext,
@@ -510,6 +522,7 @@ declare module "../../../types/hooks.js" {
510
522
  * @param next A function to get remappings from other sources (including default behavior).
511
523
  * @returns An array of remapping sources, each containing an array of remapping strings
512
524
  * and the source path they came from.
525
+ * @deprecated This hook will soon be removed.
513
526
  */
514
527
  readNpmPackageRemappings: (
515
528
  context: HookContext,
@@ -523,5 +536,61 @@ declare module "../../../types/hooks.js" {
523
536
  nextPackagePath: string,
524
537
  ) => Promise<Array<{ remappings: string[]; source: string }>>,
525
538
  ) => Promise<Array<{ remappings: string[]; source: string }>>;
539
+
540
+ /**
541
+ * Sequential hook run when a solidity build finished successfully,
542
+ * and the artifacts are ready to be processed by a plugin.
543
+ *
544
+ * This hook is only run for the "contracts" scope, and never includes
545
+ * test artifacts in its parameters.
546
+ *
547
+ * @param context The hook context.
548
+ * @param artifactPaths All the contract artifact paths, including
549
+ * pre-existing ones.
550
+ * @param buildRootFilePaths The root file paths provided to the build,
551
+ * as absolute paths or `npm:<package-name>/<file-path>` identifiers. In
552
+ * unified mode this may include test files.
553
+ * @param buildOptions The resolved options used during the build, with
554
+ * the build system's defaults filled in for any field the caller didn't
555
+ * provide.
556
+ */
557
+ processArtifactsAfterSuccessfulBuild(
558
+ context: HookContext,
559
+ artifactPaths: readonly string[],
560
+ buildRootFilePaths: readonly string[],
561
+ buildOptions: Readonly<ResolvedBuildOptions>,
562
+ ): Promise<void>;
563
+
564
+ /**
565
+ * A hook run to get and potentially process the errors of the compiler
566
+ * output after it was run by the build system.
567
+ *
568
+ * This hook allows plugin authors to process the compiler output errors
569
+ * list before it's used by the build system to report them to the users,
570
+ * but it doesn't let plugins alter the logic that determines if a
571
+ * compilation job succeeded or failed.
572
+ *
573
+ * This hook must not mutate the parameters passed to `next`. Doing so can
574
+ * have unexpected behavior, and will eventually crash Hardhat.
575
+ *
576
+ * The recommended way to use this hook is to call `next` first, and then
577
+ * work with the returned list.
578
+ *
579
+ * @param context The hook context.
580
+ * @param compilationJob The compilation job run by the build system.
581
+ * @param compilerOutput The output returned by the compiler.
582
+ * @param next A function to call the next handler for this hook.
583
+ * @returns The processed compiler output error list.
584
+ */
585
+ getCompilationJobErrors(
586
+ context: HookContext,
587
+ compilationJob: Readonly<CompilationJob>,
588
+ compilerOutput: Readonly<CompilerOutput>,
589
+ next: (
590
+ nextContext: HookContext,
591
+ nextCompilationJob: Readonly<CompilationJob>,
592
+ nextCompilerOutput: Readonly<CompilerOutput>,
593
+ ) => Promise<CompilerOutputError[]>,
594
+ ): Promise<CompilerOutputError[]>;
526
595
  }
527
596
  }
@@ -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
- }
@@ -7,6 +7,8 @@ import type { Task } from "../../../types/tasks.js";
7
7
 
8
8
  import { camelToKebabCase } from "@nomicfoundation/hardhat-utils/string";
9
9
 
10
+ import { isArgumentRequired } from "../../core/tasks/utils.js";
11
+
10
12
  export const GLOBAL_NAME_PADDING = 6;
11
13
 
12
14
  interface ArgumentDescriptor {
@@ -84,11 +86,16 @@ export function parseOptions(task: Task): {
84
86
  });
85
87
  }
86
88
 
87
- for (const { name, description, defaultValue } of task.positionalArguments) {
89
+ for (const {
90
+ name,
91
+ description,
92
+ defaultValue,
93
+ type,
94
+ } of task.positionalArguments) {
88
95
  positionalArguments.push({
89
96
  name,
90
97
  description: trimFullStop(description),
91
- isRequired: defaultValue === undefined,
98
+ isRequired: isArgumentRequired(type, defaultValue),
92
99
  ...(defaultValue !== undefined && {
93
100
  defaultValue: Array.isArray(defaultValue)
94
101
  ? defaultValue.join(", ")
@@ -14,11 +14,16 @@ import {
14
14
  copy,
15
15
  ensureDir,
16
16
  exists,
17
+ getAllFilesMatching,
17
18
  isDirectory,
18
19
  mkdir,
19
20
  readJsonFile,
21
+ remove,
22
+ symlink,
20
23
  writeJsonFile,
24
+ writeUtf8File,
21
25
  } from "@nomicfoundation/hardhat-utils/fs";
26
+ import { findClosestPackageRoot } from "@nomicfoundation/hardhat-utils/package";
22
27
  import { resolveFromRoot } from "@nomicfoundation/hardhat-utils/path";
23
28
  import * as semver from "semver";
24
29
 
@@ -35,6 +40,7 @@ import { sendErrorTelemetry } from "../telemetry/error-reporter/reporter.js";
35
40
  import {
36
41
  getDevDependenciesInstallationCommand,
37
42
  getPackageManager,
43
+ getVersion,
38
44
  installsPeerDependenciesByDefault,
39
45
  } from "./package-manager.js";
40
46
  import {
@@ -590,6 +596,10 @@ export async function copyProjectFiles(
590
596
  await copy(absoluteTemplatePath, absoluteWorkspacePath);
591
597
  }
592
598
 
599
+ await installSkills(workspace, template, force);
600
+ await createClaudeMd(workspace, force);
601
+ await createDotClaude(workspace);
602
+
593
603
  console.log(`✨ ${styleText("cyan", `Template files copied`)} ✨`);
594
604
  }
595
605
 
@@ -659,6 +669,133 @@ export async function copyProjectFilesNonInteractive(
659
669
  absoluteWorkspacePath,
660
670
  );
661
671
  }
672
+
673
+ await installSkills(workspace, template);
674
+ await createClaudeMd(workspace);
675
+ await createDotClaude(workspace);
676
+ }
677
+
678
+ /**
679
+ * Skills published from `packages/hardhat/skills/` and the npm package whose presence
680
+ * in a template's dependencies opts that template into installing the skill.
681
+ * Each skill is tied to exactly one package so they can be versioned and
682
+ * upgraded independently.
683
+ */
684
+ const SKILL_PACKAGES: ReadonlyArray<{
685
+ packageName: string;
686
+ skillName: string;
687
+ }> = [
688
+ { packageName: "hardhat", skillName: "hardhat" },
689
+ {
690
+ packageName: "@nomicfoundation/hardhat-toolbox-viem",
691
+ skillName: "hardhat-toolbox-viem",
692
+ },
693
+ {
694
+ packageName: "@nomicfoundation/hardhat-toolbox-mocha-ethers",
695
+ skillName: "hardhat-toolbox-mocha-ethers",
696
+ },
697
+ ];
698
+
699
+ /**
700
+ * For agent-aware templates (those that ship an `AGENTS.md`), copies any
701
+ * skills from `packages/hardhat/skills/` whose corresponding package is a template
702
+ * dependency into `<workspace>/.agents/skills/<skill-name>/`.
703
+ */
704
+ async function installSkills(
705
+ workspace: string,
706
+ template: Template,
707
+ force?: boolean,
708
+ ): Promise<void> {
709
+ if (!template.files.includes("AGENTS.md")) {
710
+ return;
711
+ }
712
+
713
+ const deps = {
714
+ ...template.packageJson.dependencies,
715
+ ...template.packageJson.devDependencies,
716
+ };
717
+ const relevantSkills = SKILL_PACKAGES.filter(
718
+ ({ packageName }) => deps[packageName] !== undefined,
719
+ );
720
+ if (relevantSkills.length === 0) {
721
+ return;
722
+ }
723
+
724
+ const hardhatPackageRoot = await findClosestPackageRoot(import.meta.url);
725
+ const skillsRoot = path.join(hardhatPackageRoot, "skills");
726
+
727
+ for (const { skillName } of relevantSkills) {
728
+ const skillSrcDir = path.join(skillsRoot, skillName);
729
+ const skillDestDir = path.join(workspace, ".agents", "skills", skillName);
730
+ const skillFiles = await getAllFilesMatching(skillSrcDir);
731
+ for (const file of skillFiles) {
732
+ const dest = path.join(skillDestDir, path.relative(skillSrcDir, file));
733
+ if (force !== true && (await exists(dest))) {
734
+ continue;
735
+ }
736
+ await ensureDir(path.dirname(dest));
737
+ await copy(file, dest);
738
+ }
739
+ }
740
+ }
741
+
742
+ /**
743
+ * Creates a `CLAUDE.md` file if an `AGENTS.md` file exists. Uses a symlink
744
+ * except on Windows, where a file is created that references `AGENTS.md`.
745
+ * Overwrites an existing `CLAUDE.md` only if `force` is true.
746
+ */
747
+ async function createClaudeMd(
748
+ workspace: string,
749
+ force?: boolean,
750
+ ): Promise<void> {
751
+ const agentsMdPath = path.join(workspace, "AGENTS.md");
752
+ if (!(await exists(agentsMdPath))) {
753
+ return;
754
+ }
755
+
756
+ const claudeMdPath = path.join(workspace, "CLAUDE.md");
757
+ if (await exists(claudeMdPath, { followSymlinks: false })) {
758
+ if (force !== true) {
759
+ return;
760
+ }
761
+ await remove(claudeMdPath);
762
+ }
763
+
764
+ if (process.platform === "win32") {
765
+ await writeUtf8File(claudeMdPath, "@AGENTS.md\n");
766
+ } else {
767
+ await symlink("AGENTS.md", claudeMdPath);
768
+ }
769
+ }
770
+
771
+ /**
772
+ * Creates `.claude` if `.agents` exists. Uses a symlink except on Windows,
773
+ * where the whole directory is copied. Does nothing if `.claude` already
774
+ * exists; the `force` flag is intentionally not used here because
775
+ * overwriting depends on whether the existing `.claude` is a file, directory,
776
+ * or symlink, and on the OS, which is more complexity than is worthwhile.
777
+ */
778
+ async function createDotClaude(workspace: string): Promise<void> {
779
+ const agentsDirPath = path.join(workspace, ".agents");
780
+ if (!(await exists(agentsDirPath))) {
781
+ return;
782
+ }
783
+
784
+ const claudeDirPath = path.join(workspace, ".claude");
785
+ if (await exists(claudeDirPath, { followSymlinks: false })) {
786
+ return;
787
+ }
788
+
789
+ if (process.platform === "win32") {
790
+ const agentsFiles = await getAllFilesMatching(agentsDirPath);
791
+ for (const file of agentsFiles) {
792
+ const dest = path.join(claudeDirPath, path.relative(agentsDirPath, file));
793
+ await ensureDir(path.dirname(dest));
794
+ await copy(file, dest);
795
+ }
796
+ } else {
797
+ await symlink(".agents", claudeDirPath);
798
+ }
662
799
  }
663
800
 
664
801
  /**
@@ -694,6 +831,8 @@ export async function installProjectDependencies({
694
831
  );
695
832
 
696
833
  const packageManager = getPackageManager();
834
+ const packageManagerVersion = await getVersion(workspace, packageManager);
835
+ const packageManagerMajorVersion = packageManagerVersion?.[0];
697
836
 
698
837
  // Find the template dev dependencies that are not already installed
699
838
  const templateDependencies = template.packageJson.devDependencies ?? {};
@@ -730,6 +869,7 @@ export async function installProjectDependencies({
730
869
  const command = getDevDependenciesInstallationCommand(
731
870
  packageManager,
732
871
  dependenciesToInstall,
872
+ packageManagerMajorVersion,
733
873
  );
734
874
  const commandString = command.join(" ");
735
875
 
@@ -788,6 +928,7 @@ export async function installProjectDependencies({
788
928
  const command = getDevDependenciesInstallationCommand(
789
929
  packageManager,
790
930
  dependenciesToUpdate,
931
+ packageManagerMajorVersion,
791
932
  );
792
933
  const commandString = command.join(" ");
793
934
 
@@ -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,
@@ -40,6 +40,7 @@ import { parseArgumentValue } from "../core/arguments.js";
40
40
  import { buildGlobalOptionDefinitions } from "../core/global-options.js";
41
41
  import { resolveProjectRoot } from "../core/hre.js";
42
42
  import { resolvePluginList } from "../core/plugins/resolve-plugin-list.js";
43
+ import { isArgumentRequired } from "../core/tasks/utils.js";
43
44
  import { setGlobalHardhatRuntimeEnvironment } from "../global-hre-instance.js";
44
45
  import { createHardhatRuntimeEnvironment } from "../hre-initialization.js";
45
46
 
@@ -318,7 +319,7 @@ export async function main(
318
319
  }
319
320
  } catch (error) {
320
321
  ensureError(error);
321
- printErrorMessages(error, builtinGlobalOptions?.showStackTraces);
322
+ await printErrorMessages(error, builtinGlobalOptions?.showStackTraces);
322
323
 
323
324
  try {
324
325
  await sendErrorTelemetry(error);
@@ -767,8 +768,9 @@ function validateRequiredArguments(
767
768
  taskArguments: TaskArguments,
768
769
  ) {
769
770
  const missingRequiredArgument = argumentDefinitions.find(
770
- ({ defaultValue, name }) =>
771
- defaultValue === undefined && taskArguments[name] === undefined,
771
+ ({ defaultValue, name, type }) =>
772
+ isArgumentRequired(type, defaultValue) &&
773
+ taskArguments[name] === undefined,
772
774
  );
773
775
 
774
776
  if (missingRequiredArgument === undefined) {
@@ -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(