Skip to content

Commit 82946e3

Browse files
authored
Merge pull request #6426 from NomicFoundation/hardhat-init-reinstall
Fix: bug in semver check on installProjectDependencies
2 parents 0d98efd + 83c0156 commit 82946e3

File tree

3 files changed

+274
-8
lines changed

3 files changed

+274
-8
lines changed

.changeset/cool-waves-wonder.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"hardhat": patch
3+
---
4+
5+
Fix unnecessary re-install of hardhat during init ([#6323](https://github.com/NomicFoundation/hardhat/issues/6323))

v-next/hardhat/src/internal/cli/init/init.ts

+38-8
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@ import type { PackageJson } from "@nomicfoundation/hardhat-utils/package";
33

44
import path from "node:path";
55

6-
import { HardhatError } from "@nomicfoundation/hardhat-errors";
6+
import {
7+
assertHardhatInvariant,
8+
HardhatError,
9+
} from "@nomicfoundation/hardhat-errors";
710
import {
811
copy,
912
ensureDir,
@@ -480,13 +483,9 @@ export async function installProjectDependencies(
480483

481484
// Finding the installed dependencies that have an incompatible version
482485
const dependenciesToUpdate = templateDependencyEntries
483-
.filter(([name, version]) => {
484-
const workspaceVersion = workspaceDependencies[name];
485-
return (
486-
workspaceVersion !== undefined &&
487-
!semver.satisfies(version, workspaceVersion) &&
488-
!semver.intersects(version, workspaceVersion)
489-
);
486+
.filter(([dependencyName, templateVersion]) => {
487+
const workspaceVersion = workspaceDependencies[dependencyName];
488+
return shouldUpdateDependency(workspaceVersion, templateVersion);
490489
})
491490
.map(([name, version]) => `${name}@${version}`);
492491

@@ -529,3 +528,34 @@ function showStarOnGitHubMessage() {
529528
console.log();
530529
console.log(chalk.cyan(" https://github.com/NomicFoundation/hardhat"));
531530
}
531+
532+
// NOTE: This function is exported for testing purposes only.
533+
export function shouldUpdateDependency(
534+
workspaceVersion: string | undefined,
535+
templateVersion: string,
536+
): boolean {
537+
// We should not update the dependency if it is not yet installed in the workspace.
538+
if (workspaceVersion === undefined) {
539+
return false;
540+
}
541+
// NOTE: a specific version also a valid range that includes itself only
542+
const workspaceRange = semver.validRange(workspaceVersion, {
543+
includePrerelease: true,
544+
});
545+
const templateRange = semver.validRange(templateVersion, {
546+
includePrerelease: true,
547+
});
548+
assertHardhatInvariant(
549+
templateRange !== null,
550+
"All dependencies of the template should have valid versions",
551+
);
552+
// We should update the dependency if the workspace version could not be parsed.
553+
if (workspaceRange === null) {
554+
return true;
555+
}
556+
// We should update the dependency if the workspace range (or, in particular, a specific version) is not
557+
// a strict subset of the template range/does not equal the template version.
558+
return !semver.subset(workspaceRange, templateRange, {
559+
includePrerelease: true,
560+
});
561+
}

v-next/hardhat/test/internal/cli/init/init.ts

+231
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { Template } from "../../../../src/internal/cli/init/template.js";
12
import type { PackageJson } from "@nomicfoundation/hardhat-utils/package";
23

34
import assert from "node:assert/strict";
@@ -28,6 +29,7 @@ import {
2829
printWelcomeMessage,
2930
relativeTemplateToWorkspacePath,
3031
relativeWorkspaceToTemplatePath,
32+
shouldUpdateDependency,
3133
} from "../../../../src/internal/cli/init/init.js";
3234
import { getTemplates } from "../../../../src/internal/cli/init/template.js";
3335

@@ -311,6 +313,72 @@ describe("installProjectDependencies", async () => {
311313
}
312314
},
313315
);
316+
317+
it(
318+
"should not update dependencies if they are up-to-date and the user opts-in to the update (specific version)",
319+
{
320+
skip: process.env.HARDHAT_DISABLE_SLOW_TESTS === "true",
321+
},
322+
async () => {
323+
const template: Template = {
324+
name: "test",
325+
packageJson: {
326+
name: "test",
327+
version: "0.0.1",
328+
devDependencies: { "fake-dependency": "^1.2.3" }, // <-- required version
329+
},
330+
path: process.cwd(),
331+
files: [],
332+
};
333+
334+
await writeUtf8File(
335+
"package.json",
336+
JSON.stringify({
337+
type: "module",
338+
devDependencies: { "fake-dependency": "1.2.3" }, // <-- specific version
339+
}),
340+
);
341+
await installProjectDependencies(process.cwd(), template, false, true);
342+
343+
assert.ok(
344+
!(await exists("node_modules")),
345+
"no modules should have been installed",
346+
);
347+
},
348+
);
349+
350+
it(
351+
"should not update dependencies if they are up-to-date and the user opts-in to the update (version range)",
352+
{
353+
skip: process.env.HARDHAT_DISABLE_SLOW_TESTS === "true",
354+
},
355+
async () => {
356+
const template: Template = {
357+
name: "test",
358+
packageJson: {
359+
name: "test",
360+
version: "0.0.1",
361+
devDependencies: { "fake-dependency": ">=1.2.3" }, // <-- required version
362+
},
363+
path: process.cwd(),
364+
files: [],
365+
};
366+
367+
await writeUtf8File(
368+
"package.json",
369+
JSON.stringify({
370+
type: "module",
371+
devDependencies: { "fake-dependency": "^1.2.3" }, // <-- version range
372+
}),
373+
);
374+
await installProjectDependencies(process.cwd(), template, false, true);
375+
376+
assert.ok(
377+
!(await exists("node_modules")),
378+
"no modules should have been installed",
379+
);
380+
},
381+
);
314382
});
315383

316384
describe("initHardhat", async () => {
@@ -347,3 +415,166 @@ describe("initHardhat", async () => {
347415
);
348416
}
349417
});
418+
419+
describe("shouldUpdateDependency", () => {
420+
const testCases = [
421+
{
422+
workspaceVersion: "1.0.0",
423+
templateVersion: "1.0.0",
424+
expectedResult: false,
425+
},
426+
{
427+
workspaceVersion: "1.0.0",
428+
templateVersion: "1.2.3",
429+
expectedResult: true,
430+
},
431+
{
432+
workspaceVersion: "1.2.3",
433+
templateVersion: "1.0.0",
434+
expectedResult: true,
435+
},
436+
{
437+
workspaceVersion: "1.2.3",
438+
templateVersion: "^1.2.3",
439+
expectedResult: false,
440+
},
441+
{
442+
workspaceVersion: "^1.2.3",
443+
templateVersion: "1.2.3",
444+
expectedResult: true,
445+
},
446+
{
447+
workspaceVersion: ">= 1.2.3",
448+
templateVersion: "^1.2.3",
449+
expectedResult: true,
450+
},
451+
{
452+
workspaceVersion: "^1.2.3",
453+
templateVersion: ">= 1.2.3",
454+
expectedResult: false,
455+
},
456+
{
457+
workspaceVersion: "1.0.0-dev",
458+
templateVersion: "1.0.0-dev",
459+
expectedResult: false,
460+
},
461+
{
462+
workspaceVersion: "1.0.0-dev",
463+
templateVersion: "1.2.3-dev",
464+
expectedResult: true,
465+
},
466+
{
467+
workspaceVersion: "1.2.3-dev",
468+
templateVersion: "1.0.0-dev",
469+
expectedResult: true,
470+
},
471+
{
472+
workspaceVersion: "1.2.3-dev",
473+
templateVersion: "^1.2.3-dev",
474+
expectedResult: false,
475+
},
476+
{
477+
workspaceVersion: "^1.2.3-dev",
478+
templateVersion: "1.2.3-dev",
479+
expectedResult: true,
480+
},
481+
{
482+
workspaceVersion: ">= 1.2.3-dev",
483+
templateVersion: "^1.2.3-dev",
484+
expectedResult: true,
485+
},
486+
{
487+
workspaceVersion: "^1.2.3-dev",
488+
templateVersion: ">= 1.2.3-dev",
489+
expectedResult: false,
490+
},
491+
{
492+
workspaceVersion: "1.0.0",
493+
templateVersion: "1.0.0-dev",
494+
expectedResult: true,
495+
},
496+
{
497+
workspaceVersion: "1.0.0-dev",
498+
templateVersion: "1.0.0",
499+
expectedResult: true,
500+
},
501+
{
502+
workspaceVersion: "1.0.0-dev",
503+
templateVersion: "1.2.3",
504+
expectedResult: true,
505+
},
506+
{
507+
workspaceVersion: "1.0.0",
508+
templateVersion: "1.2.3-dev",
509+
expectedResult: true,
510+
},
511+
{
512+
workspaceVersion: "1.2.3",
513+
templateVersion: "1.0.0-dev",
514+
expectedResult: true,
515+
},
516+
{
517+
workspaceVersion: "1.2.3-dev",
518+
templateVersion: "1.0.0",
519+
expectedResult: true,
520+
},
521+
{
522+
workspaceVersion: "1.2.3",
523+
templateVersion: "^1.2.3-dev",
524+
expectedResult: false,
525+
},
526+
{
527+
workspaceVersion: "1.2.3-dev",
528+
templateVersion: "^1.2.3",
529+
expectedResult: true,
530+
},
531+
{
532+
workspaceVersion: "^1.2.3",
533+
templateVersion: "1.2.3-dev",
534+
expectedResult: true,
535+
},
536+
{
537+
workspaceVersion: "^1.2.3-dev",
538+
templateVersion: "1.2.3",
539+
expectedResult: true,
540+
},
541+
{
542+
workspaceVersion: ">= 1.2.3",
543+
templateVersion: "^1.2.3-dev",
544+
expectedResult: true,
545+
},
546+
{
547+
workspaceVersion: ">= 1.2.3-dev",
548+
templateVersion: "^1.2.3",
549+
expectedResult: true,
550+
},
551+
{
552+
workspaceVersion: "^1.2.3",
553+
templateVersion: ">= 1.2.3-dev",
554+
expectedResult: false,
555+
},
556+
{
557+
workspaceVersion: "^1.2.3-dev",
558+
templateVersion: ">= 1.2.3",
559+
expectedResult: true,
560+
},
561+
{
562+
workspaceVersion: "3.0.0-next.0",
563+
templateVersion: "^3.0.0-next.0",
564+
expectedResult: false,
565+
},
566+
];
567+
568+
for (const {
569+
workspaceVersion,
570+
templateVersion,
571+
expectedResult,
572+
} of testCases) {
573+
it(`should return ${expectedResult} when workspace version is ${workspaceVersion} and template version is ${templateVersion}`, () => {
574+
assert.equal(
575+
shouldUpdateDependency(workspaceVersion, templateVersion),
576+
expectedResult,
577+
);
578+
});
579+
}
580+
});

0 commit comments

Comments
 (0)