hardhat 3.5.1 → 3.7.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.
- package/CHANGELOG.md +34 -0
- package/dist/src/internal/builtin-plugins/flatten/task-action.d.ts.map +1 -1
- package/dist/src/internal/builtin-plugins/flatten/task-action.js +23 -7
- package/dist/src/internal/builtin-plugins/flatten/task-action.js.map +1 -1
- package/dist/src/internal/builtin-plugins/solidity/build-system/build-system.d.ts +1 -1
- package/dist/src/internal/builtin-plugins/solidity/build-system/build-system.d.ts.map +1 -1
- package/dist/src/internal/builtin-plugins/solidity/build-system/build-system.js +78 -1
- package/dist/src/internal/builtin-plugins/solidity/build-system/build-system.js.map +1 -1
- package/dist/src/internal/builtin-plugins/solidity/tasks/build.d.ts.map +1 -1
- package/dist/src/internal/builtin-plugins/solidity/tasks/build.js +1 -5
- package/dist/src/internal/builtin-plugins/solidity/tasks/build.js.map +1 -1
- package/dist/src/internal/builtin-plugins/solidity/type-extensions.d.ts +53 -1
- package/dist/src/internal/builtin-plugins/solidity/type-extensions.d.ts.map +1 -1
- package/dist/src/internal/cli/help/utils.d.ts.map +1 -1
- package/dist/src/internal/cli/help/utils.js +3 -2
- package/dist/src/internal/cli/help/utils.js.map +1 -1
- package/dist/src/internal/cli/init/init.d.ts.map +1 -1
- package/dist/src/internal/cli/init/init.js +110 -1
- package/dist/src/internal/cli/init/init.js.map +1 -1
- package/dist/src/internal/cli/init/prompt.js +1 -1
- package/dist/src/internal/cli/init/prompt.js.map +1 -1
- package/dist/src/internal/cli/main.d.ts.map +1 -1
- package/dist/src/internal/cli/main.js +3 -1
- package/dist/src/internal/cli/main.js.map +1 -1
- package/dist/src/internal/core/tasks/resolved-task.d.ts.map +1 -1
- package/dist/src/internal/core/tasks/resolved-task.js +3 -2
- package/dist/src/internal/core/tasks/resolved-task.js.map +1 -1
- package/dist/src/internal/core/tasks/utils.d.ts +3 -0
- package/dist/src/internal/core/tasks/utils.d.ts.map +1 -1
- package/dist/src/internal/core/tasks/utils.js +6 -0
- package/dist/src/internal/core/tasks/utils.js.map +1 -1
- package/dist/src/internal/core/tasks/validations.js +3 -3
- package/dist/src/internal/core/tasks/validations.js.map +1 -1
- package/dist/src/types/solidity/build-system.d.ts +22 -2
- package/dist/src/types/solidity/build-system.d.ts.map +1 -1
- package/dist/src/types/solidity/build-system.js.map +1 -1
- package/package.json +4 -3
- package/skills/hardhat/SKILL.md +152 -0
- package/skills/hardhat-toolbox-mocha-ethers/SKILL.md +119 -0
- package/skills/hardhat-toolbox-viem/SKILL.md +126 -0
- package/src/internal/builtin-plugins/flatten/task-action.ts +37 -7
- package/src/internal/builtin-plugins/solidity/build-system/build-system.ts +124 -6
- package/src/internal/builtin-plugins/solidity/hook-handlers/hre.ts +1 -1
- package/src/internal/builtin-plugins/solidity/tasks/build.ts +1 -6
- package/src/internal/builtin-plugins/solidity/type-extensions.ts +69 -0
- package/src/internal/cli/help/utils.ts +9 -2
- package/src/internal/cli/init/init.ts +136 -0
- package/src/internal/cli/init/prompt.ts +1 -1
- package/src/internal/cli/main.ts +4 -2
- package/src/internal/core/tasks/resolved-task.ts +5 -2
- package/src/internal/core/tasks/utils.ts +13 -0
- package/src/internal/core/tasks/validations.ts +3 -3
- package/src/types/solidity/build-system.ts +24 -5
- package/templates/hardhat-3/01-node-test-runner-viem/AGENTS.md +20 -0
- package/templates/hardhat-3/01-node-test-runner-viem/README.md +3 -3
- package/templates/hardhat-3/01-node-test-runner-viem/package.json +3 -3
- package/templates/hardhat-3/02-mocha-ethers/AGENTS.md +20 -0
- package/templates/hardhat-3/02-mocha-ethers/README.md +3 -3
- package/templates/hardhat-3/02-mocha-ethers/package.json +3 -3
- package/templates/hardhat-3/03-minimal/README.md +2 -2
- package/templates/hardhat-3/03-minimal/package.json +1 -1
|
@@ -29,6 +29,7 @@ import type {
|
|
|
29
29
|
CompilerOutputError,
|
|
30
30
|
DependencyGraph,
|
|
31
31
|
SolidityBuildInfo,
|
|
32
|
+
ResolvedBuildOptions,
|
|
32
33
|
} from "../../../../types/solidity.js";
|
|
33
34
|
|
|
34
35
|
import os from "node:os";
|
|
@@ -277,13 +278,14 @@ export class SolidityBuildSystemImplementation implements SolidityBuildSystem {
|
|
|
277
278
|
rootFilePaths: string[],
|
|
278
279
|
options?: BuildOptions,
|
|
279
280
|
): Promise<CompilationJobCreationError | Map<string, FileBuildResult>> {
|
|
280
|
-
const resolvedOptions:
|
|
281
|
+
const resolvedOptions: ResolvedBuildOptions = {
|
|
281
282
|
buildProfile: DEFAULT_BUILD_PROFILE,
|
|
282
283
|
concurrency: Math.max(os.cpus().length - 1, 1),
|
|
283
284
|
force: false,
|
|
284
285
|
isolated: false,
|
|
285
286
|
quiet: false,
|
|
286
287
|
scope: "contracts",
|
|
288
|
+
cleanupArtifacts: false,
|
|
287
289
|
...options,
|
|
288
290
|
};
|
|
289
291
|
|
|
@@ -368,6 +370,8 @@ export class SolidityBuildSystemImplementation implements SolidityBuildSystem {
|
|
|
368
370
|
ReadonlyMap<string, string[]>
|
|
369
371
|
> = new Map();
|
|
370
372
|
|
|
373
|
+
let contractArtifactsAfterBuild: string[] | undefined;
|
|
374
|
+
|
|
371
375
|
if (isSuccessfulBuild) {
|
|
372
376
|
log("Emitting artifacts of successful build");
|
|
373
377
|
await Promise.all(
|
|
@@ -397,6 +401,43 @@ export class SolidityBuildSystemImplementation implements SolidityBuildSystem {
|
|
|
397
401
|
);
|
|
398
402
|
|
|
399
403
|
await saveCache(this.#options.cachePath, this.#compileCache);
|
|
404
|
+
|
|
405
|
+
if (resolvedOptions.cleanupArtifacts) {
|
|
406
|
+
const paths = await this.cleanupArtifacts(rootFilePaths, {
|
|
407
|
+
scope: resolvedOptions.scope,
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
if (resolvedOptions.scope !== "tests") {
|
|
411
|
+
contractArtifactsAfterBuild = paths;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
if (resolvedOptions.scope !== "tests") {
|
|
416
|
+
if (
|
|
417
|
+
await this.#hooks.hasHandlers(
|
|
418
|
+
"solidity",
|
|
419
|
+
"processArtifactsAfterSuccessfulBuild",
|
|
420
|
+
)
|
|
421
|
+
) {
|
|
422
|
+
contractArtifactsAfterBuild ??=
|
|
423
|
+
await this.#getCurrentContractArtifactPaths();
|
|
424
|
+
|
|
425
|
+
if (!this.#options.solidityConfig.splitTestsCompilation) {
|
|
426
|
+
// In unified mode the contracts artifacts directory also contains
|
|
427
|
+
// test artifacts, so we must filter them out before invoking the
|
|
428
|
+
// processArtifactsAfterSuccessfulBuild hook.
|
|
429
|
+
contractArtifactsAfterBuild = await this.#filterOutTestArtifacts(
|
|
430
|
+
contractArtifactsAfterBuild,
|
|
431
|
+
);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
await this.#hooks.runSequentialHandlers(
|
|
435
|
+
"solidity",
|
|
436
|
+
"processArtifactsAfterSuccessfulBuild",
|
|
437
|
+
[contractArtifactsAfterBuild, rootFilePaths, resolvedOptions],
|
|
438
|
+
);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
400
441
|
}
|
|
401
442
|
|
|
402
443
|
spinner.stop();
|
|
@@ -415,10 +456,16 @@ export class SolidityBuildSystemImplementation implements SolidityBuildSystem {
|
|
|
415
456
|
"We emitted contract artifacts for all the jobs if the build was successful",
|
|
416
457
|
);
|
|
417
458
|
|
|
418
|
-
const errors = await
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
459
|
+
const errors = await this.#hooks.runHandlerChain(
|
|
460
|
+
"solidity",
|
|
461
|
+
"getCompilationJobErrors",
|
|
462
|
+
[result.compilationJob, result.compilerOutput],
|
|
463
|
+
async (_context, nextCompilationJob, nextCompilerOutput) =>
|
|
464
|
+
await Promise.all(
|
|
465
|
+
(nextCompilerOutput.errors ?? []).map((error) =>
|
|
466
|
+
this.remapCompilerError(nextCompilationJob, error, true),
|
|
467
|
+
),
|
|
468
|
+
),
|
|
422
469
|
);
|
|
423
470
|
|
|
424
471
|
this.#printSolcErrorsAndWarnings(errors);
|
|
@@ -1064,7 +1111,7 @@ export class SolidityBuildSystemImplementation implements SolidityBuildSystem {
|
|
|
1064
1111
|
public async cleanupArtifacts(
|
|
1065
1112
|
rootFilePaths: string[],
|
|
1066
1113
|
options: { scope?: BuildScope } = {},
|
|
1067
|
-
): Promise<
|
|
1114
|
+
): Promise<string[]> {
|
|
1068
1115
|
const scope = options.scope ?? "contracts";
|
|
1069
1116
|
|
|
1070
1117
|
this.#ensureSplitCompilationModeIfTestsScope(scope);
|
|
@@ -1191,6 +1238,77 @@ export class SolidityBuildSystemImplementation implements SolidityBuildSystem {
|
|
|
1191
1238
|
async () => {},
|
|
1192
1239
|
);
|
|
1193
1240
|
}
|
|
1241
|
+
|
|
1242
|
+
return artifactPaths;
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
/**
|
|
1246
|
+
* Returns every `.json` artifact under the contracts artifacts directory,
|
|
1247
|
+
* excluding the project-wide top-level files (e.g. `artifacts.d.ts`) and
|
|
1248
|
+
* the `build-info/` subtree.
|
|
1249
|
+
*
|
|
1250
|
+
* In unified mode, the returned list also contains test artifacts. Callers
|
|
1251
|
+
* that need a contracts-only view should pass the result through
|
|
1252
|
+
* `#filterOutTestArtifacts`.
|
|
1253
|
+
*/
|
|
1254
|
+
async #getCurrentContractArtifactPaths(): Promise<string[]> {
|
|
1255
|
+
const artifactsDirectory = await this.getArtifactsDirectory("contracts");
|
|
1256
|
+
const buildInfosDir = path.join(artifactsDirectory, `build-info`);
|
|
1257
|
+
|
|
1258
|
+
const allArtifactFiles = await getAllFilesMatching(
|
|
1259
|
+
artifactsDirectory,
|
|
1260
|
+
(p) => {
|
|
1261
|
+
// Ignore top level files (e.g. the project-wide artifacts.d.ts)
|
|
1262
|
+
if (
|
|
1263
|
+
p.indexOf(path.sep, artifactsDirectory.length + path.sep.length) ===
|
|
1264
|
+
-1
|
|
1265
|
+
) {
|
|
1266
|
+
return false;
|
|
1267
|
+
}
|
|
1268
|
+
return p.endsWith(".json");
|
|
1269
|
+
},
|
|
1270
|
+
(dir) => dir !== buildInfosDir,
|
|
1271
|
+
);
|
|
1272
|
+
|
|
1273
|
+
return allArtifactFiles;
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
/**
|
|
1277
|
+
* Returns `artifactPaths` with the test artifacts removed.
|
|
1278
|
+
*
|
|
1279
|
+
* This is only meaningful in unified mode, where contracts and tests share
|
|
1280
|
+
* the same artifacts directory. The caller is responsible for ensuring that
|
|
1281
|
+
* precondition.
|
|
1282
|
+
*
|
|
1283
|
+
* Each artifact's user source name is recovered from its path and classified
|
|
1284
|
+
* via `getScope`. Results are cached per source name to avoid repeating the
|
|
1285
|
+
* FS stat work for sibling artifacts. npm-style source names don't resolve
|
|
1286
|
+
* to a real path inside the project, so `getScope` falls back to
|
|
1287
|
+
* `"contracts"`, which is the intended outcome.
|
|
1288
|
+
*/
|
|
1289
|
+
async #filterOutTestArtifacts(artifactPaths: string[]): Promise<string[]> {
|
|
1290
|
+
const artifactsDirectory = await this.getArtifactsDirectory("contracts");
|
|
1291
|
+
const scopeBySource = new Map<string, BuildScope>();
|
|
1292
|
+
const contractArtifactPaths: string[] = [];
|
|
1293
|
+
|
|
1294
|
+
for (const artifactPath of artifactPaths) {
|
|
1295
|
+
const userSourceName = toForwardSlash(
|
|
1296
|
+
path.relative(artifactsDirectory, path.dirname(artifactPath)),
|
|
1297
|
+
);
|
|
1298
|
+
|
|
1299
|
+
let scope = scopeBySource.get(userSourceName);
|
|
1300
|
+
if (scope === undefined) {
|
|
1301
|
+
const fsPath = path.resolve(this.#options.projectRoot, userSourceName);
|
|
1302
|
+
scope = await this.getScope(fsPath);
|
|
1303
|
+
scopeBySource.set(userSourceName, scope);
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
if (scope === "contracts") {
|
|
1307
|
+
contractArtifactPaths.push(artifactPath);
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
return contractArtifactPaths;
|
|
1194
1312
|
}
|
|
1195
1313
|
|
|
1196
1314
|
public async compileBuildInfo(
|
|
@@ -114,7 +114,7 @@ class LazySolidityBuildSystem implements SolidityBuildSystem {
|
|
|
114
114
|
public async cleanupArtifacts(
|
|
115
115
|
rootFilePaths: string[],
|
|
116
116
|
options: { scope?: BuildScope } = {},
|
|
117
|
-
): Promise<
|
|
117
|
+
): Promise<string[]> {
|
|
118
118
|
const buildSystem = await this.#getBuildSystem();
|
|
119
119
|
return await buildSystem.cleanupArtifacts(rootFilePaths, options);
|
|
120
120
|
}
|
|
@@ -186,6 +186,7 @@ async function runSolidityBuild({
|
|
|
186
186
|
buildProfile,
|
|
187
187
|
quiet,
|
|
188
188
|
scope,
|
|
189
|
+
cleanupArtifacts: isFullBuild,
|
|
189
190
|
},
|
|
190
191
|
);
|
|
191
192
|
|
|
@@ -194,12 +195,6 @@ async function runSolidityBuild({
|
|
|
194
195
|
// We use the result keys in case a hook added or removed root files
|
|
195
196
|
const builtRootPaths = [...results.keys()];
|
|
196
197
|
|
|
197
|
-
if (isFullBuild) {
|
|
198
|
-
await solidity.cleanupArtifacts(builtRootPaths, {
|
|
199
|
-
scope,
|
|
200
|
-
});
|
|
201
|
-
}
|
|
202
|
-
|
|
203
198
|
const preBuildRoots = new Set([...contractRootPaths, ...testRootPaths]);
|
|
204
199
|
if (
|
|
205
200
|
builtRootPaths.length === preBuildRoots.size &&
|
|
@@ -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
|
}
|
|
@@ -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 {
|
|
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
|
|
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
|
|
|
@@ -591,6 +596,10 @@ export async function copyProjectFiles(
|
|
|
591
596
|
await copy(absoluteTemplatePath, absoluteWorkspacePath);
|
|
592
597
|
}
|
|
593
598
|
|
|
599
|
+
await installSkills(workspace, template, force);
|
|
600
|
+
await createClaudeMd(workspace, force);
|
|
601
|
+
await createDotClaude(workspace);
|
|
602
|
+
|
|
594
603
|
console.log(`✨ ${styleText("cyan", `Template files copied`)} ✨`);
|
|
595
604
|
}
|
|
596
605
|
|
|
@@ -660,6 +669,133 @@ export async function copyProjectFilesNonInteractive(
|
|
|
660
669
|
absoluteWorkspacePath,
|
|
661
670
|
);
|
|
662
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
|
+
}
|
|
663
799
|
}
|
|
664
800
|
|
|
665
801
|
/**
|
package/src/internal/cli/main.ts
CHANGED
|
@@ -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
|
|
|
@@ -767,8 +768,9 @@ function validateRequiredArguments(
|
|
|
767
768
|
taskArguments: TaskArguments,
|
|
768
769
|
) {
|
|
769
770
|
const missingRequiredArgument = argumentDefinitions.find(
|
|
770
|
-
({ defaultValue, name }) =>
|
|
771
|
-
defaultValue
|
|
771
|
+
({ defaultValue, name, type }) =>
|
|
772
|
+
isArgumentRequired(type, defaultValue) &&
|
|
773
|
+
taskArguments[name] === undefined,
|
|
772
774
|
);
|
|
773
775
|
|
|
774
776
|
if (missingRequiredArgument === undefined) {
|
|
@@ -22,7 +22,7 @@ import { ensureError } from "@nomicfoundation/hardhat-utils/error";
|
|
|
22
22
|
|
|
23
23
|
import { detectPluginNpmDependencyProblems } from "../plugins/detect-plugin-npm-dependency-problems.js";
|
|
24
24
|
|
|
25
|
-
import { formatTaskId } from "./utils.js";
|
|
25
|
+
import { formatTaskId, isArgumentRequired } from "./utils.js";
|
|
26
26
|
import { validateTaskArgumentValue } from "./validations.js";
|
|
27
27
|
|
|
28
28
|
export class ResolvedTask implements Task {
|
|
@@ -203,7 +203,10 @@ export class ResolvedTask implements Task {
|
|
|
203
203
|
argument: PositionalArgumentDefinition,
|
|
204
204
|
value: ArgumentValue | ArgumentValue[],
|
|
205
205
|
) {
|
|
206
|
-
if (
|
|
206
|
+
if (
|
|
207
|
+
isArgumentRequired(argument.type, argument.defaultValue) &&
|
|
208
|
+
value === undefined
|
|
209
|
+
) {
|
|
207
210
|
throw new HardhatError(
|
|
208
211
|
HardhatError.ERRORS.CORE.TASK_DEFINITIONS.MISSING_VALUE_FOR_TASK_ARGUMENT,
|
|
209
212
|
{
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { ArgumentType, ArgumentValue } from "../../../types/arguments.js";
|
|
2
|
+
|
|
1
3
|
export function formatTaskId(taskId: string | string[]): string {
|
|
2
4
|
if (typeof taskId === "string") {
|
|
3
5
|
return taskId;
|
|
@@ -9,3 +11,14 @@ export function formatTaskId(taskId: string | string[]): string {
|
|
|
9
11
|
export function getActorFragment(pluginId: string | undefined): string {
|
|
10
12
|
return pluginId !== undefined ? `Plugin ${pluginId} is` : "You are";
|
|
11
13
|
}
|
|
14
|
+
|
|
15
|
+
export function isOptionalArgumentType(type: ArgumentType): boolean {
|
|
16
|
+
return type.endsWith("_WITHOUT_DEFAULT");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function isArgumentRequired(
|
|
20
|
+
type: ArgumentType,
|
|
21
|
+
defaultValue: ArgumentValue | ArgumentValue[] | undefined,
|
|
22
|
+
): boolean {
|
|
23
|
+
return defaultValue === undefined && !isOptionalArgumentType(type);
|
|
24
|
+
}
|
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
validateArgumentValue,
|
|
20
20
|
} from "../arguments.js";
|
|
21
21
|
|
|
22
|
-
import { formatTaskId } from "./utils.js";
|
|
22
|
+
import { isArgumentRequired, formatTaskId } from "./utils.js";
|
|
23
23
|
|
|
24
24
|
export function validateId(id: string | string[]): void {
|
|
25
25
|
if (id.length === 0) {
|
|
@@ -133,8 +133,8 @@ export function validatePositionalArgument(
|
|
|
133
133
|
|
|
134
134
|
if (
|
|
135
135
|
lastArg !== undefined &&
|
|
136
|
-
lastArg.defaultValue
|
|
137
|
-
defaultValue
|
|
136
|
+
!isArgumentRequired(lastArg.type, lastArg.defaultValue) &&
|
|
137
|
+
isArgumentRequired(type, defaultValue)
|
|
138
138
|
) {
|
|
139
139
|
throw new HardhatError(
|
|
140
140
|
HardhatError.ERRORS.CORE.TASK_DEFINITIONS.REQUIRED_ARG_AFTER_OPTIONAL,
|
|
@@ -52,18 +52,36 @@ export interface BuildOptions {
|
|
|
52
52
|
* and produce separate compilation passes.
|
|
53
53
|
*/
|
|
54
54
|
scope?: BuildScope;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* If `true`, a clean up process is run after a successful build.
|
|
58
|
+
*
|
|
59
|
+
* The clean up deletes all the orphan artifacts that aren't reachable from
|
|
60
|
+
* the provided `rootFilePaths`.
|
|
61
|
+
*
|
|
62
|
+
* When building with `"contracts"` scope, it also updates the top-level
|
|
63
|
+
* `artifacts.d.ts`.
|
|
64
|
+
*
|
|
65
|
+
* Only use this option when the provided `rootFilePaths` represent the
|
|
66
|
+
* entire set of contracts for the scope you are using (i.e. not during
|
|
67
|
+
* partial builds). Otherwise, you'll delete artifacts generated in previous
|
|
68
|
+
* builds, despite their sources still being available.
|
|
69
|
+
*/
|
|
70
|
+
cleanupArtifacts?: boolean;
|
|
55
71
|
}
|
|
56
72
|
|
|
73
|
+
/**
|
|
74
|
+
* The resolved BuildOptions used to run a build.
|
|
75
|
+
*/
|
|
76
|
+
export type ResolvedBuildOptions = Required<BuildOptions>;
|
|
77
|
+
|
|
57
78
|
/**
|
|
58
79
|
* The options of the `getCompilationJobs` method.
|
|
59
80
|
*
|
|
60
81
|
* Note that this option object includes a `quiet` property, as this process
|
|
61
82
|
* may require downloading compilers, and potentially printing some output.
|
|
62
83
|
*/
|
|
63
|
-
export type GetCompilationJobsOptions = Omit<
|
|
64
|
-
BuildOptions,
|
|
65
|
-
"removeUnusedArtifacts"
|
|
66
|
-
>;
|
|
84
|
+
export type GetCompilationJobsOptions = Omit<BuildOptions, "cleanupArtifacts">;
|
|
67
85
|
|
|
68
86
|
/**
|
|
69
87
|
* The options of the `runCompilationJob` method.
|
|
@@ -434,11 +452,12 @@ export interface SolidityBuildSystem {
|
|
|
434
452
|
* used.
|
|
435
453
|
*
|
|
436
454
|
* @param rootFilePaths All the root files of the project.
|
|
455
|
+
* @returns The list of artifact paths that remain after the cleanup.
|
|
437
456
|
*/
|
|
438
457
|
cleanupArtifacts(
|
|
439
458
|
rootFilePaths: string[],
|
|
440
459
|
options?: { scope?: BuildScope },
|
|
441
|
-
): Promise<
|
|
460
|
+
): Promise<string[]>;
|
|
442
461
|
|
|
443
462
|
/**
|
|
444
463
|
* Compiles a build info, returning the output of the compilation, verbatim,
|