Skip to content

Commit dba8f0e

Browse files
authored
feat: add forge for project bootstrapping (#135)
1 parent e4eef4c commit dba8f0e

File tree

2 files changed

+146
-31
lines changed

2 files changed

+146
-31
lines changed

src/commands/create/groups/quickstart.ts

+78-16
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@ import { packageManagers } from "../../../utils/packageManager.js";
66
import { isPrivateKey } from "../../../utils/validators.js";
77
import { askForTemplate, setupTemplate, askForPackageManager, successfulMessage, getUniqueValues } from "../utils.js";
88

9+
import type { PackageManagerType } from "../../../utils/packageManager.js";
910
import type { GenericTemplate } from "../index.js";
1011

11-
type Template = GenericTemplate & {
12-
framework: "Hardhat";
13-
ethereumFramework: "Ethers v5" | "Ethers v6";
12+
export type Template = GenericTemplate & {
13+
framework: "Hardhat" | "Foundry";
14+
ethereumFramework: "Ethers v5" | "Ethers v6" | "Solidity";
1415
language: "Solidity" | "Vyper";
1516
};
1617

@@ -60,8 +61,69 @@ export const templates: Template[] = [
6061
path: "templates/quickstart/hardhat/paymaster",
6162
git: "https://github.com/matter-labs/zksync-contract-templates/",
6263
},
64+
{
65+
name: "Quickstart - Foundry",
66+
value: "qs-fs-hello-zksync",
67+
framework: "Foundry",
68+
ethereumFramework: "Solidity",
69+
language: "Solidity",
70+
path: "templates/quickstart/foundry/hello-zksync",
71+
git: "https://github.com/matter-labs/zksync-contract-templates/",
72+
},
73+
{
74+
name: "Quickstart - Foundry",
75+
value: "qs-fs-factories",
76+
framework: "Foundry",
77+
ethereumFramework: "Solidity",
78+
language: "Solidity",
79+
path: "templates/quickstart/foundry/factory",
80+
git: "https://github.com/matter-labs/zksync-contract-templates/",
81+
},
82+
{
83+
name: "Quickstart - Foundry",
84+
value: "qs-fs-testing",
85+
framework: "Foundry",
86+
ethereumFramework: "Solidity",
87+
language: "Solidity",
88+
path: "templates/quickstart/foundry/testing",
89+
git: "https://github.com/matter-labs/zksync-contract-templates/",
90+
},
6391
];
6492

93+
const logFoundryInfo = () => {
94+
const contractsDir = "/src";
95+
const deploymentScriptsDir = "/script";
96+
const tipMessage =
97+
"- Tip: You can use the " + chalk.blueBright("--rpc-url") + " option to specify the network to deploy to.";
98+
const deployCommand = `- Deploy your contract: ${chalk.blueBright("forge script [OPTIONS] <PATH> [ARGS] --zksync")}`;
99+
const directoryOverview = `${chalk.magentaBright("Directory Overview:")}
100+
- Contracts: ${contractsDir}
101+
- Deployment Scripts: ${deploymentScriptsDir}`;
102+
const commandsOverview = `${chalk.magentaBright("Commands:")}
103+
- Compile your contracts: ${chalk.blueBright("forge build --zksync")}
104+
${deployCommand}
105+
${tipMessage}`;
106+
107+
Logger.info(`${directoryOverview}\n\n${commandsOverview}`);
108+
};
109+
110+
const logHardhatInfo = (packageManager: PackageManagerType) => {
111+
const contractsDir = "/contracts";
112+
const deploymentScriptsDir = "/deploy";
113+
const tipMessage =
114+
"- Tip: You can use the " + chalk.blueBright("--network") + " option to specify the network to deploy to.";
115+
const deployCommand = `- Deploy your contract: ${chalk.blueBright(packageManagers[packageManager].run("deploy"))}`;
116+
const directoryOverview = `${chalk.magentaBright("Directory Overview:")}
117+
- Contracts: ${contractsDir}
118+
- Deployment Scripts: ${deploymentScriptsDir}`;
119+
const commandsOverview = `${chalk.magentaBright("Commands:")}
120+
- Compile your contracts: ${chalk.blueBright(packageManagers[packageManager].run("compile"))}
121+
${deployCommand}
122+
${tipMessage}`;
123+
124+
Logger.info(`${directoryOverview}\n\n${commandsOverview}`);
125+
};
126+
65127
export default async (folderLocation: string, folderRelativePath: string, templateKey?: string) => {
66128
let env: Record<string, string> = {};
67129
let template: Template;
@@ -99,17 +161,17 @@ export default async (folderLocation: string, folderRelativePath: string, templa
99161
...env,
100162
WALLET_PRIVATE_KEY: privateKey,
101163
};
102-
const packageManager = await askForPackageManager();
103-
await setupTemplate(template, folderLocation, env, packageManager);
104-
105-
successfulMessage.start(folderRelativePath);
106-
Logger.info(`${chalk.magentaBright("Directory Overview:")}
107-
- Contracts: /contracts
108-
- Deployment Scripts: /deploy
109-
110-
${chalk.magentaBright("Commands:")}
111-
- Compile your contracts: ${chalk.blueBright(packageManagers[packageManager].run("compile"))}
112-
- Deploy your contract: ${chalk.blueBright(packageManagers[packageManager].run("deploy"))}
113-
- Tip: You can use the ${chalk.blueBright("--network")} option to specify the network to deploy to.`);
114-
successfulMessage.end(folderRelativePath);
164+
let packageManager: PackageManagerType | undefined;
165+
if (template.framework === "Foundry") {
166+
await setupTemplate(template, folderLocation, env);
167+
successfulMessage.start(folderRelativePath);
168+
logFoundryInfo();
169+
successfulMessage.end(folderRelativePath);
170+
} else {
171+
packageManager = await askForPackageManager();
172+
await setupTemplate(template, folderLocation, env, packageManager);
173+
successfulMessage.start(folderRelativePath);
174+
logHardhatInfo(packageManager);
175+
successfulMessage.end(folderRelativePath);
176+
}
115177
};

src/commands/create/utils.ts

+68-15
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { executeCommand } from "../../utils/helpers.js";
1010
import Logger from "../../utils/logger.js";
1111
import { packageManagers } from "../../utils/packageManager.js";
1212

13+
import type { Template } from "./groups/quickstart.js";
1314
import type { GenericTemplate } from "./index.js";
1415
import type { PackageManagerType } from "../../utils/packageManager.js";
1516

@@ -96,9 +97,61 @@ export const setupTemplate = async (
9697
template: GenericTemplate,
9798
folderLocation: string,
9899
env: Record<string, string>,
99-
packageManager: PackageManagerType
100+
packageManager?: PackageManagerType
100101
) => {
101102
Logger.info(`\nSetting up template in ${chalk.magentaBright(folderLocation)}...`);
103+
const typedTemplate = template as Template;
104+
if (typedTemplate.framework === "Foundry") {
105+
await setupFoundryProject(template, folderLocation);
106+
return;
107+
}
108+
109+
await setupHardhatProject(template, folderLocation);
110+
111+
if (Object.keys(env).length > 0) {
112+
await setupEnvironmentVariables(folderLocation, env);
113+
}
114+
if (packageManager) {
115+
await installDependencies(packageManager, folderLocation);
116+
} else {
117+
throw new Error("Package manager is required to install dependencies.");
118+
}
119+
};
120+
// Sets up a foundry project by initializing it with the specified template.
121+
// Primarily only used for foundry quickstart templates.
122+
const setupFoundryProject = async (template: GenericTemplate, folderLocation: string) => {
123+
const spinner = ora("Initializing foundry project...").start();
124+
125+
try {
126+
const isInstalled = await executeCommand("forge --version", { silent: true });
127+
// TODO: https://github.com/matter-labs/zksync-cli/issues/137
128+
if (!isInstalled) {
129+
spinner.fail(
130+
"Forge is not installed from the `foundry-zksync` repository. Please visit the official installation guide at https://github.com/matter-labs/foundry-zksync and follow the instructions to install it. Once installed, try running the command again."
131+
);
132+
return;
133+
}
134+
135+
const cloneTempPath = path.join(folderLocation, "___temp");
136+
await executeCommand(`forge init --template ${template.git} ${cloneTempPath}`, { silent: true });
137+
138+
const templatePath = path.join(cloneTempPath, template.path || "");
139+
if (!fileOrDirExists(templatePath)) {
140+
throw new Error(`The specified template path does not exist: ${templatePath}`);
141+
}
142+
143+
copyRecursiveSync(templatePath, folderLocation);
144+
fs.rmSync(cloneTempPath, { recursive: true, force: true });
145+
146+
spinner.succeed("Foundry project initialized successfully.");
147+
} catch (error) {
148+
spinner.fail("Failed to initialize foundry project");
149+
throw error;
150+
}
151+
};
152+
153+
// Sets up a Hardhat project by cloning the specified template and copying it to the specified folder location.
154+
const setupHardhatProject = async (template: GenericTemplate, folderLocation: string) => {
102155
if (!template.path) {
103156
const spinner = ora("Cloning template...").start();
104157
try {
@@ -128,14 +181,11 @@ export const setupTemplate = async (
128181
const cloneTempPath = path.join(folderLocation, "___temp");
129182
const spinner = ora("Cloning template...").start();
130183
try {
131-
await cloneRepo(template.git, path.join(folderLocation, "___temp"), { silent: true });
132-
184+
await cloneRepo(template.git, cloneTempPath, { silent: true });
133185
const templatePath = path.join(cloneTempPath, template.path);
134186
if (fileOrDirExists(templatePath)) {
135187
try {
136-
// Copy the template to the folder location
137188
copyRecursiveSync(templatePath, folderLocation);
138-
// Remove the temp folder after copying
139189
fs.rmSync(cloneTempPath, { recursive: true, force: true });
140190
} catch (err) {
141191
throw new Error("An error occurred while copying the template");
@@ -149,17 +199,20 @@ export const setupTemplate = async (
149199
throw error;
150200
}
151201
}
152-
if (Object.keys(env).length > 0) {
153-
const spinner = ora("Setting up environment variables...").start();
154-
try {
155-
setupEnv(folderLocation, env);
156-
} catch (error) {
157-
spinner.fail("Failed to set up environment variables");
158-
throw error;
159-
}
160-
spinner.succeed("Environment variables set up");
202+
};
203+
// Sets up environment variables in the specified folder location.
204+
const setupEnvironmentVariables = async (folderLocation: string, env: Record<string, string>) => {
205+
const spinner = ora("Setting up environment variables...").start();
206+
try {
207+
setupEnv(folderLocation, env);
208+
} catch (error) {
209+
spinner.fail("Failed to set up environment variables");
210+
throw error;
161211
}
162-
212+
spinner.succeed("Environment variables set up");
213+
};
214+
// Install dependencies with the specified package manager in the specified folder location.
215+
const installDependencies = async (packageManager: PackageManagerType, folderLocation: string) => {
163216
const spinner = ora(
164217
`Installing dependencies with ${chalk.bold(packageManager)}... This may take a couple minutes.`
165218
).start();

0 commit comments

Comments
 (0)