hardhat
Version:
Hardhat is an extensible developer tool that helps smart contract developers increase productivity by reliably bringing together the tools they want.
269 lines (224 loc) • 7.18 kB
text/typescript
import boxen from "boxen";
import picocolors from "picocolors";
import fsExtra from "fs-extra";
import { join } from "node:path";
import { mkdir, readFile, writeFile } from "node:fs/promises";
import semver from "semver";
import { getCacheDir } from "../util/global-dir";
import { getHardhatVersion } from "../util/packageInfo";
const GITHUB_API_URL = "https://api.github.com";
const GITHUB_OWNER = "NomicFoundation";
const GITHUB_REPO = "hardhat";
const V3_RELEASE_TAG = "hardhat@3.0.0";
const V3_RELEASE_VERSION_NOTIFIER_ASSET_NAME = "version-notifier-message.txt";
const V3_RELEASE_MAX_TIMES_SHOWN = 5;
const CURRENT_HARDHAT_MAJOR_VERSION = 2;
const boxenOptions = {
padding: 1,
borderStyle: "round",
borderColor: "yellow",
} as const;
interface VersionNotifierCache {
lastCheck: string | 0;
v3TimesShown: number;
v3Release?: Release;
v3ReleaseMessage?: string;
}
/* eslint-disable @typescript-eslint/naming-convention */
interface Release {
name: string;
tag_name: string;
draft: boolean;
prerelease: boolean;
published_at: string;
html_url: string;
assets: Array<{
name: string;
browser_download_url: string;
}>;
body: string; // release notes
}
/* eslint-enable @typescript-eslint/naming-convention */
export async function showNewVersionNotification() {
const cache = await readCache();
const lastCheckDate = new Date(cache.lastCheck);
const now = new Date();
const oneDay = 1000 * 60 * 60 * 24;
if (now.getTime() - lastCheckDate.getTime() < oneDay) {
return;
}
const hardhatVersion = getHardhatVersion();
const releases = await getReleases();
const sortedV2Versions = releases
// filter and map releases to versions
.flatMap((release) => {
const [packageName, rawPackageVersion] = release.tag_name.split("@");
const packageVersion = semver.valid(rawPackageVersion);
// filter out a release if:
// - it's not a hardhat-core release
// - it's a draft or a prerelease
// - the version is invalid
// - the major version is not the current major
if (
packageName !== GITHUB_REPO ||
release.draft ||
release.prerelease ||
packageVersion === null ||
semver.major(packageVersion) !== CURRENT_HARDHAT_MAJOR_VERSION
) {
return [];
}
return [packageVersion];
})
// sort in descending order by version
.sort((releaseAVersion, releaseBVersion) => {
return semver.rcompare(releaseAVersion, releaseBVersion);
});
const latestV2Version: string | undefined = sortedV2Versions[0];
const v3Release = cache.v3Release ?? (await getV3Release());
if (latestV2Version === undefined && v3Release === undefined) {
// this should never happen unless the github api is down
return;
}
if (
latestV2Version !== undefined &&
semver.gt(latestV2Version, hardhatVersion)
) {
let installationCommand = "npm install";
if (await fsExtra.pathExists("yarn.lock")) {
installationCommand = "yarn add";
} else if (await fsExtra.pathExists("pnpm-lock.yaml")) {
installationCommand = "pnpm install";
}
console.log(
boxen(
`New Hardhat release available! ${picocolors.red(
hardhatVersion
)} -> ${picocolors.green(latestV2Version)}.
Changelog: https://hardhat.org/release/${latestV2Version}
Run "${installationCommand} hardhat@latest" to update.`,
boxenOptions
)
);
}
if (
v3Release !== undefined &&
cache.v3TimesShown < V3_RELEASE_MAX_TIMES_SHOWN
) {
const releaseVersion = semver.valid(v3Release.tag_name.split("@")[1]);
if (releaseVersion !== null) {
cache.v3ReleaseMessage ??= await getV3ReleaseMessage(v3Release);
if (cache.v3ReleaseMessage !== undefined) {
console.log(boxen(cache.v3ReleaseMessage, boxenOptions));
cache.v3TimesShown++;
}
}
}
await writeCache({
...cache,
lastCheck: now.toISOString(),
v3Release,
});
}
async function readCache(): Promise<VersionNotifierCache> {
const cacheDir = await getCacheDir();
const versionNotifierCachePath = join(cacheDir, "version-notifier.json");
let cache: VersionNotifierCache = {
lastCheck: 0, // new Date(0) represents the unix epoch
v3TimesShown: 0,
};
try {
const fileContents = await readFile(versionNotifierCachePath, "utf-8");
const { lastCheck, v3TimesShown } = JSON.parse(fileContents);
cache = {
lastCheck: typeof lastCheck === "string" ? lastCheck : 0,
v3TimesShown: typeof v3TimesShown === "number" ? v3TimesShown : 0,
};
} catch (error: any) {
// We don't care if it fails
}
return cache;
}
async function writeCache(cache: VersionNotifierCache) {
const cacheDir = await getCacheDir();
const versionNotifierCachePath = join(cacheDir, "version-notifier.json");
try {
await mkdir(cacheDir, { recursive: true });
await writeFile(versionNotifierCachePath, JSON.stringify(cache, null, 2));
} catch (error) {
// We don't care if it fails
}
}
async function getReleases(): Promise<Release[]> {
const { request } = await import("undici");
let releases: Release[] = [];
try {
const githubResponse = await request(
`${GITHUB_API_URL}/repos/${GITHUB_OWNER}/${GITHUB_REPO}/releases`,
{
method: "GET",
headers: {
"User-Agent": "Hardhat",
"X-GitHub-Api-Version": "2022-11-28",
},
query: {
per_page: 100,
},
}
);
releases = (await githubResponse.body.json()) as Release[];
} catch (error: any) {
// We don't care if it fails
}
return releases;
}
async function getV3Release(): Promise<Release | undefined> {
const { request } = await import("undici");
let v3Release: Release | undefined;
try {
const githubResponse = await request(
`${GITHUB_API_URL}/repos/${GITHUB_OWNER}/${GITHUB_REPO}/releases/tags/${V3_RELEASE_TAG}`,
{
method: "GET",
headers: {
"User-Agent": "Hardhat",
"X-GitHub-Api-Version": "2022-11-28",
},
}
);
const jsonResponse = (await githubResponse.body.json()) as any;
if (jsonResponse.message === "Not Found") {
// eslint-disable-next-line @nomicfoundation/hardhat-internal-rules/only-hardhat-error
throw new Error("Not Found");
}
v3Release = jsonResponse as Release;
} catch (error: any) {
// We don't care if it fails
}
return v3Release;
}
async function getV3ReleaseMessage(
v3Release: Release
): Promise<string | undefined> {
const { request } = await import("undici");
const versionNotifierAsset = v3Release.assets.find(
({ name }) => name === V3_RELEASE_VERSION_NOTIFIER_ASSET_NAME
);
if (versionNotifierAsset === undefined) {
return;
}
let v3ReleaseMessage;
try {
const githubResponse = await request(
versionNotifierAsset.browser_download_url,
{
method: "GET",
maxRedirections: 10,
}
);
v3ReleaseMessage = await githubResponse.body.text();
} catch (error: any) {
// We don't care if it fails
}
return v3ReleaseMessage;
}