Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix: bug in semver check on installProjectDependencies #6426

Merged
merged 5 commits into from
Mar 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/cool-waves-wonder.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"hardhat": patch
---

Fix unnecessary re-install of hardhat during init ([#6323](https://github.com/NomicFoundation/hardhat/issues/6323))
46 changes: 38 additions & 8 deletions v-next/hardhat/src/internal/cli/init/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ import type { PackageJson } from "@nomicfoundation/hardhat-utils/package";

import path from "node:path";

import { HardhatError } from "@nomicfoundation/hardhat-errors";
import {
assertHardhatInvariant,
HardhatError,
} from "@nomicfoundation/hardhat-errors";
import {
copy,
ensureDir,
Expand Down Expand Up @@ -480,13 +483,9 @@ export async function installProjectDependencies(

// Finding the installed dependencies that have an incompatible version
const dependenciesToUpdate = templateDependencyEntries
.filter(([name, version]) => {
const workspaceVersion = workspaceDependencies[name];
return (
workspaceVersion !== undefined &&
!semver.satisfies(version, workspaceVersion) &&
!semver.intersects(version, workspaceVersion)
);
.filter(([dependencyName, templateVersion]) => {
const workspaceVersion = workspaceDependencies[dependencyName];
return shouldUpdateDependency(workspaceVersion, templateVersion);
})
.map(([name, version]) => `${name}@${version}`);

Expand Down Expand Up @@ -529,3 +528,34 @@ function showStarOnGitHubMessage() {
console.log();
console.log(chalk.cyan(" https://github.com/NomicFoundation/hardhat"));
}

// NOTE: This function is exported for testing purposes only.
export function shouldUpdateDependency(
workspaceVersion: string | undefined,
templateVersion: string,
): boolean {
// We should not update the dependency if it is not yet installed in the workspace.
if (workspaceVersion === undefined) {
return false;
}
// NOTE: a specific version also a valid range that includes itself only
const workspaceRange = semver.validRange(workspaceVersion, {
includePrerelease: true,
});
const templateRange = semver.validRange(templateVersion, {
includePrerelease: true,
});
assertHardhatInvariant(
templateRange !== null,
"All dependencies of the template should have valid versions",
);
// We should update the dependency if the workspace version could not be parsed.
if (workspaceRange === null) {
return true;
}
// We should update the dependency if the workspace range (or, in particular, a specific version) is not
// a strict subset of the template range/does not equal the template version.
return !semver.subset(workspaceRange, templateRange, {
includePrerelease: true,
});
}
231 changes: 231 additions & 0 deletions v-next/hardhat/test/internal/cli/init/init.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { Template } from "../../../../src/internal/cli/init/template.js";
import type { PackageJson } from "@nomicfoundation/hardhat-utils/package";

import assert from "node:assert/strict";
Expand Down Expand Up @@ -28,6 +29,7 @@ import {
printWelcomeMessage,
relativeTemplateToWorkspacePath,
relativeWorkspaceToTemplatePath,
shouldUpdateDependency,
} from "../../../../src/internal/cli/init/init.js";
import { getTemplates } from "../../../../src/internal/cli/init/template.js";

Expand Down Expand Up @@ -311,6 +313,72 @@ describe("installProjectDependencies", async () => {
}
},
);

it(
"should not update dependencies if they are up-to-date and the user opts-in to the update (specific version)",
{
skip: process.env.HARDHAT_DISABLE_SLOW_TESTS === "true",
},
async () => {
const template: Template = {
name: "test",
packageJson: {
name: "test",
version: "0.0.1",
devDependencies: { "fake-dependency": "^1.2.3" }, // <-- required version
},
path: process.cwd(),
files: [],
};

await writeUtf8File(
"package.json",
JSON.stringify({
type: "module",
devDependencies: { "fake-dependency": "1.2.3" }, // <-- specific version
}),
);
await installProjectDependencies(process.cwd(), template, false, true);

assert.ok(
!(await exists("node_modules")),
"no modules should have been installed",
);
},
);

it(
"should not update dependencies if they are up-to-date and the user opts-in to the update (version range)",
{
skip: process.env.HARDHAT_DISABLE_SLOW_TESTS === "true",
},
async () => {
const template: Template = {
name: "test",
packageJson: {
name: "test",
version: "0.0.1",
devDependencies: { "fake-dependency": ">=1.2.3" }, // <-- required version
},
path: process.cwd(),
files: [],
};

await writeUtf8File(
"package.json",
JSON.stringify({
type: "module",
devDependencies: { "fake-dependency": "^1.2.3" }, // <-- version range
}),
);
await installProjectDependencies(process.cwd(), template, false, true);

assert.ok(
!(await exists("node_modules")),
"no modules should have been installed",
);
},
);
});

describe("initHardhat", async () => {
Expand Down Expand Up @@ -347,3 +415,166 @@ describe("initHardhat", async () => {
);
}
});

describe("shouldUpdateDependency", () => {
const testCases = [
{
workspaceVersion: "1.0.0",
templateVersion: "1.0.0",
expectedResult: false,
},
{
workspaceVersion: "1.0.0",
templateVersion: "1.2.3",
expectedResult: true,
},
{
workspaceVersion: "1.2.3",
templateVersion: "1.0.0",
expectedResult: true,
},
{
workspaceVersion: "1.2.3",
templateVersion: "^1.2.3",
expectedResult: false,
},
{
workspaceVersion: "^1.2.3",
templateVersion: "1.2.3",
expectedResult: true,
},
{
workspaceVersion: ">= 1.2.3",
templateVersion: "^1.2.3",
expectedResult: true,
},
{
workspaceVersion: "^1.2.3",
templateVersion: ">= 1.2.3",
expectedResult: false,
},
{
workspaceVersion: "1.0.0-dev",
templateVersion: "1.0.0-dev",
expectedResult: false,
},
{
workspaceVersion: "1.0.0-dev",
templateVersion: "1.2.3-dev",
expectedResult: true,
},
{
workspaceVersion: "1.2.3-dev",
templateVersion: "1.0.0-dev",
expectedResult: true,
},
{
workspaceVersion: "1.2.3-dev",
templateVersion: "^1.2.3-dev",
expectedResult: false,
},
{
workspaceVersion: "^1.2.3-dev",
templateVersion: "1.2.3-dev",
expectedResult: true,
},
{
workspaceVersion: ">= 1.2.3-dev",
templateVersion: "^1.2.3-dev",
expectedResult: true,
},
{
workspaceVersion: "^1.2.3-dev",
templateVersion: ">= 1.2.3-dev",
expectedResult: false,
},
{
workspaceVersion: "1.0.0",
templateVersion: "1.0.0-dev",
expectedResult: true,
},
{
workspaceVersion: "1.0.0-dev",
templateVersion: "1.0.0",
expectedResult: true,
},
{
workspaceVersion: "1.0.0-dev",
templateVersion: "1.2.3",
expectedResult: true,
},
{
workspaceVersion: "1.0.0",
templateVersion: "1.2.3-dev",
expectedResult: true,
},
{
workspaceVersion: "1.2.3",
templateVersion: "1.0.0-dev",
expectedResult: true,
},
{
workspaceVersion: "1.2.3-dev",
templateVersion: "1.0.0",
expectedResult: true,
},
{
workspaceVersion: "1.2.3",
templateVersion: "^1.2.3-dev",
expectedResult: false,
},
{
workspaceVersion: "1.2.3-dev",
templateVersion: "^1.2.3",
expectedResult: true,
},
{
workspaceVersion: "^1.2.3",
templateVersion: "1.2.3-dev",
expectedResult: true,
},
{
workspaceVersion: "^1.2.3-dev",
templateVersion: "1.2.3",
expectedResult: true,
},
{
workspaceVersion: ">= 1.2.3",
templateVersion: "^1.2.3-dev",
expectedResult: true,
},
{
workspaceVersion: ">= 1.2.3-dev",
templateVersion: "^1.2.3",
expectedResult: true,
},
{
workspaceVersion: "^1.2.3",
templateVersion: ">= 1.2.3-dev",
expectedResult: false,
},
{
workspaceVersion: "^1.2.3-dev",
templateVersion: ">= 1.2.3",
expectedResult: true,
},
{
workspaceVersion: "3.0.0-next.0",
templateVersion: "^3.0.0-next.0",
expectedResult: false,
},
];

for (const {
workspaceVersion,
templateVersion,
expectedResult,
} of testCases) {
it(`should return ${expectedResult} when workspace version is ${workspaceVersion} and template version is ${templateVersion}`, () => {
assert.equal(
shouldUpdateDependency(workspaceVersion, templateVersion),
expectedResult,
);
});
}
});
Loading