Skip to content

Commit 64c6d05

Browse files
create logic for init command + tests
1 parent 2a364d1 commit 64c6d05

File tree

15 files changed

+629
-15
lines changed

15 files changed

+629
-15
lines changed

pnpm-lock.yaml

+6
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

v-next/hardhat-errors/src/descriptors.ts

+60
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,56 @@ Please double check whether you have multiple versions of the same plugin instal
162162
websiteTitle: "Invalid BigInt",
163163
websiteDescription: `Given value was not a valid BigInt.`,
164164
},
165+
HARDHAT_PROJECT_ALREADY_CREATED: {
166+
number: 12,
167+
messageTemplate:
168+
"You are trying to initialize a project inside an existing Hardhat project. The path to the project's configuration file is: {hardhatProjectRootPath}.",
169+
websiteTitle: "Hardhat project already created",
170+
websiteDescription: `Cannot create a new Hardhat project, the current folder is already associated with a project.`,
171+
},
172+
NOT_INSIDE_PROJECT_ON_WINDOWS: {
173+
number: 13,
174+
messageTemplate: `You are not inside a project and Hardhat failed to initialize a new one.
175+
176+
If you were trying to create a new project, please try again using Windows Subsystem for Linux (WSL) or PowerShell.
177+
`,
178+
websiteTitle:
179+
"You are not inside a Hardhat project and Hardhat failed to initialize a new one",
180+
websiteDescription: `You are trying to run Hardhat outside of a Hardhat project, and we couldn't initialize one.
181+
182+
If you were trying to create a new project, please try again using Windows Subsystem for Linux (WSL) or PowerShell.
183+
184+
You can learn how to use Hardhat by reading the [Getting Started guide](/hardhat-runner/docs/getting-started).`,
185+
},
186+
NOT_IN_INTERACTIVE_SHELL: {
187+
number: 14,
188+
messageTemplate:
189+
"You are trying to initialize a project but you are not in an interactive shell.",
190+
websiteTitle: "Not inside an interactive shell",
191+
websiteDescription: `You are trying to initialize a project but you are not in an interactive shell.
192+
193+
Please re-run the command inside an interactive shell.`,
194+
},
195+
UNSUPPORTED_OPERATION: {
196+
number: 15,
197+
messageTemplate: "{operation} is not supported in Hardhat.",
198+
websiteTitle: "Unsupported operation",
199+
websiteDescription: `You are trying to perform an unsupported operation.
200+
201+
Unless you are creating a task or plugin, this is probably a bug.
202+
203+
Please [report it](https://github.com/nomiclabs/hardhat/issues/new) to help us improve Hardhat.`,
204+
},
205+
ONLY_ESM_SUPPORTED: {
206+
number: 16,
207+
messageTemplate: `Hardhat only supports ESM projects. Please be sure to specify "'type': 'module'" in your package.json`,
208+
websiteTitle: "Only ESM projects are supported",
209+
websiteDescription: `You are trying to initialize a new Hardhat project, but your package.json does not have the property "type" set to "module".
210+
211+
Currently, Hardhat only supports ESM projects.
212+
213+
Please add the property "type" with the value "module" in your package.json to ensure that your project is recognized as an ESM project.`,
214+
},
165215
},
166216
INTERNAL: {
167217
ASSERTION_ERROR: {
@@ -388,6 +438,16 @@ Please double check your arguments.`,
388438
websiteDescription: `A path to the configuration file is expected after the parameter
389439
"--config", but none was provided.
390440
441+
Please double check your arguments.`,
442+
},
443+
CANNOT_COMBINE_INIT_AND_CONFIG_PATH: {
444+
number: 309,
445+
messageTemplate:
446+
'The configuration parameter "--config" cannot be used with the "init" command',
447+
websiteTitle:
448+
'The configuration parameter "--config" cannot be used with the "init" command',
449+
websiteDescription: `The configuration parameter "--config" cannot be used with the "init" command.
450+
391451
Please double check your arguments.`,
392452
},
393453
},

v-next/hardhat/package.json

+2
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@
7373
"@nomicfoundation/hardhat-errors": "workspace:^3.0.0",
7474
"@nomicfoundation/hardhat-utils": "workspace:^3.0.0",
7575
"@nomicfoundation/hardhat-zod-utils": "workspace:^3.0.0",
76+
"chalk": "^5.3.0",
77+
"enquirer": "^2.3.0",
7678
"tsx": "^4.11.0",
7779
"zod": "^3.23.8"
7880
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { HardhatError } from "@nomicfoundation/hardhat-errors";
2+
3+
import { createProject } from "./project-creation.js";
4+
import { getUserConfigPath, isCwdInsideProject } from "./project-structure.js";
5+
6+
export async function initHardhat() {
7+
if (await isCwdInsideProject()) {
8+
throw new HardhatError(
9+
HardhatError.ERRORS.GENERAL.HARDHAT_PROJECT_ALREADY_CREATED,
10+
{
11+
hardhatProjectRootPath: await getUserConfigPath(),
12+
},
13+
);
14+
}
15+
16+
if (
17+
process.stdout.isTTY === true ||
18+
process.env.HARDHAT_CREATE_EMPTY_TYPESCRIPT_HARDHAT_CONFIG !== undefined
19+
) {
20+
await createProject();
21+
return;
22+
}
23+
24+
// Many terminal emulators in windows fail to run the createProject()
25+
// workflow, and don't present themselves as TTYs. If we are in this
26+
// situation we throw a special error instructing the user to use WSL or
27+
// powershell to initialize the project.
28+
if (process.platform === "win32") {
29+
throw new HardhatError(
30+
HardhatError.ERRORS.GENERAL.NOT_INSIDE_PROJECT_ON_WINDOWS,
31+
);
32+
}
33+
34+
throw new HardhatError(HardhatError.ERRORS.GENERAL.NOT_IN_INTERACTIVE_SHELL);
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
import type { PackageJson } from "@nomicfoundation/hardhat-utils/package";
2+
3+
import path from "node:path";
4+
5+
import { HardhatError } from "@nomicfoundation/hardhat-errors";
6+
import { ensureError } from "@nomicfoundation/hardhat-utils/error";
7+
import {
8+
exists,
9+
readJsonFile,
10+
writeJsonFile,
11+
writeUtf8File,
12+
} from "@nomicfoundation/hardhat-utils/fs";
13+
import chalk from "chalk";
14+
15+
import { getHardhatVersion } from "../../utils/package.js";
16+
17+
const HARDHAT_NAME = "Hardhat";
18+
const HARDHAT_PACKAGE_NAME = "hardhat";
19+
20+
// TODO: test that is testing the last version of solidity
21+
export const EMPTY_HARDHAT_CONFIG = `/** import type { HardhatUserConfig } from "@nomicfoundation/hardhat-core/config"; */
22+
23+
export default {
24+
solidity: "0.8.24",
25+
};
26+
`;
27+
28+
enum Action {
29+
CREATE_EMPTY_TYPESCRIPT_HARDHAT_CONFIG = "Create an empty hardhat.config.ts",
30+
QUIT = "Quit",
31+
}
32+
33+
export async function createProject() {
34+
printAsciiLogo();
35+
36+
await printWelcomeMessage();
37+
38+
const action = await getAction();
39+
if (action === Action.QUIT) {
40+
return;
41+
}
42+
43+
const packageJson = await getProjectPackageJson();
44+
if (packageJson === undefined) {
45+
await createPackageJson();
46+
}
47+
48+
if (action === Action.CREATE_EMPTY_TYPESCRIPT_HARDHAT_CONFIG) {
49+
return createEmptyTypescriptHardhatConfig();
50+
}
51+
}
52+
53+
async function getProjectPackageJson(): Promise<PackageJson | undefined> {
54+
const pathToJsonPackage = path.join(process.cwd(), "package.json");
55+
56+
if (!(await exists(pathToJsonPackage))) {
57+
return undefined;
58+
}
59+
60+
const pkg: PackageJson = await readJsonFile(pathToJsonPackage);
61+
62+
if (pkg.type === undefined || pkg.type !== "module") {
63+
throw new HardhatError(HardhatError.ERRORS.GENERAL.ONLY_ESM_SUPPORTED);
64+
}
65+
66+
return pkg;
67+
}
68+
69+
async function createEmptyTypescriptHardhatConfig() {
70+
await writeEmptyHardhatConfig();
71+
72+
console.log(`✨ ${chalk.cyan(`Config file created`)} ✨`);
73+
74+
if (!(await isInstalled(HARDHAT_PACKAGE_NAME))) {
75+
console.log("");
76+
console.log(`You need to install hardhat locally to use it. Please run:`);
77+
const cmd = await getRecommendedDependenciesInstallationCommand({
78+
[HARDHAT_PACKAGE_NAME]: `^${await getHardhatVersion()}`,
79+
});
80+
81+
console.log("");
82+
console.log(cmd.join(" "));
83+
console.log("");
84+
}
85+
86+
console.log();
87+
88+
showReminderMessages();
89+
90+
return;
91+
}
92+
93+
function showReminderMessages() {
94+
showStarOnGitHubMessage();
95+
}
96+
97+
// generated with the "colossal" font
98+
function printAsciiLogo() {
99+
console.log(
100+
chalk.blue("888 888 888 888 888"),
101+
);
102+
console.log(
103+
chalk.blue("888 888 888 888 888"),
104+
);
105+
console.log(
106+
chalk.blue("888 888 888 888 888"),
107+
);
108+
console.log(
109+
chalk.blue("8888888888 8888b. 888d888 .d88888 88888b. 8888b. 888888"),
110+
);
111+
console.log(
112+
chalk.blue('888 888 "88b 888P" d88" 888 888 "88b "88b 888'),
113+
);
114+
console.log(
115+
chalk.blue("888 888 .d888888 888 888 888 888 888 .d888888 888"),
116+
);
117+
console.log(
118+
chalk.blue("888 888 888 888 888 Y88b 888 888 888 888 888 Y88b."),
119+
);
120+
console.log(
121+
chalk.blue('888 888 "Y888888 888 "Y88888 888 888 "Y888888 "Y888'),
122+
);
123+
console.log("");
124+
}
125+
126+
async function printWelcomeMessage() {
127+
console.log(
128+
chalk.cyan(
129+
`👷 Welcome to ${HARDHAT_NAME} v${await getHardhatVersion()} 👷\n`,
130+
),
131+
);
132+
}
133+
134+
async function getAction(): Promise<Action> {
135+
// If a matching ENV variable is passed, we do not prompt the user
136+
if (
137+
process.env.HARDHAT_CREATE_EMPTY_TYPESCRIPT_HARDHAT_CONFIG !== undefined
138+
) {
139+
return Action.CREATE_EMPTY_TYPESCRIPT_HARDHAT_CONFIG;
140+
}
141+
142+
// No ENV variable is passed, we prompt the user
143+
try {
144+
const { default: enquirer } = await import("enquirer");
145+
146+
const actionResponse = await enquirer.prompt<{ action: string }>([
147+
{
148+
name: "action",
149+
type: "select",
150+
message: "What do you want to do?",
151+
initial: 0,
152+
choices: Object.values(Action).map((a: Action) => {
153+
return {
154+
name: a,
155+
message: a,
156+
value: a,
157+
};
158+
}),
159+
},
160+
]);
161+
162+
const actions: Action[] = Object.values(Action);
163+
for (const a of actions) {
164+
if (a === actionResponse.action) {
165+
return a;
166+
}
167+
}
168+
169+
throw new HardhatError(HardhatError.ERRORS.GENERAL.UNSUPPORTED_OPERATION, {
170+
operation: `Responding with "${actionResponse.action}" to the project initialization wizard`,
171+
});
172+
} catch (e) {
173+
if (e === "") {
174+
return Action.QUIT;
175+
}
176+
177+
ensureError(e);
178+
throw e;
179+
}
180+
}
181+
182+
async function createPackageJson() {
183+
await writeJsonFile("package.json", {
184+
name: "hardhat-project",
185+
type: "module",
186+
});
187+
}
188+
189+
function showStarOnGitHubMessage() {
190+
console.log(
191+
chalk.cyan("Give Hardhat a star on Github if you're enjoying it! ⭐️✨"),
192+
);
193+
console.log();
194+
console.log(chalk.cyan(" https://github.com/NomicFoundation/hardhat"));
195+
}
196+
197+
async function writeEmptyHardhatConfig() {
198+
const hardhatConfigFilename = "hardhat.config.ts";
199+
return writeUtf8File(hardhatConfigFilename, EMPTY_HARDHAT_CONFIG);
200+
}
201+
202+
async function isInstalled(dep: string) {
203+
const packageJson: PackageJson = await readJsonFile("package.json");
204+
205+
const allDependencies = {
206+
...packageJson.dependencies,
207+
...packageJson.devDependencies,
208+
...packageJson.optionalDependencies,
209+
};
210+
211+
return dep in allDependencies;
212+
}
213+
214+
async function getRecommendedDependenciesInstallationCommand(dependencies: {
215+
[name: string]: string;
216+
}): Promise<string[]> {
217+
const deps = Object.entries(dependencies).map(
218+
([name, version]) => `"${name}@${version}"`,
219+
);
220+
221+
if (await isYarnProject()) {
222+
return ["yarn", "add", "--dev", ...deps];
223+
}
224+
225+
if (await isPnpmProject()) {
226+
return ["pnpm", "add", "-D", ...deps];
227+
}
228+
229+
return ["npm", "install", "--save-dev", ...deps];
230+
}
231+
232+
async function isYarnProject() {
233+
return exists("yarn.lock");
234+
}
235+
236+
async function isPnpmProject() {
237+
return exists("pnpm-lock.yaml");
238+
}

0 commit comments

Comments
 (0)