Skip to content
Open
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
22 changes: 22 additions & 0 deletions contributors/TEMPLATING.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,28 @@ export default { one: 1, two: 2}

To avoid issues when named arguments have typos, the `withDefaults` utility will also throw an error when an argument is passed with a name that wasn't expected by the template.

### Using global variables in args file content

You can use global variables in your argument values by defining your argument values as functions.

Instead of static values, export functions that receive global variables as parameters and return the desired content.

**Important:** The function's return type must match the default argument value type defined in the template.

Example:

```js
// Static value (basic approach)
export const description = "Hello world";

// Dynamic value using global variables (advanced approach)
export const description = ({ solidityFramework }) => `Hello ${solidityFramework}`;
```

**Available global variables:**

- `solidityFramework` - The selected Solidity framework (e.g., "hardhat", "foundry")

# Args files injection in Template files

For each Template file, we search on the extensions the user selected for the existence of Args files in the exact same relative path. If Args files are found, we combine them into an array.
Expand Down
14 changes: 12 additions & 2 deletions src/tasks/copy-template-files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,13 @@ import path from "path";
import { promisify } from "util";
import link from "../utils/link";
import { getArgumentFromExternalExtensionOption } from "../utils/external-extensions";
import { BASE_DIR, SOLIDITY_FRAMEWORKS, SOLIDITY_FRAMEWORKS_DIR, EXAMPLE_CONTRACTS_DIR } from "../utils/consts";
import {
BASE_DIR,
SOLIDITY_FRAMEWORKS,
SOLIDITY_FRAMEWORKS_DIR,
EXAMPLE_CONTRACTS_DIR,
GLOBAL_ARGS_DEFAULTS,
} from "../utils/consts";

const EXTERNAL_EXTENSION_TMP_DIR = "tmp-external-extension";

Expand Down Expand Up @@ -282,7 +288,11 @@ const processTemplatedFiles = async (
return accumulated;
}, freshArgs);

const output = fileTemplate(combinedArgs);
const globalArgs = {
solidityFramework: [solidityFramework || GLOBAL_ARGS_DEFAULTS.solidityFramework],
};

const output = fileTemplate({ ...combinedArgs, ...globalArgs });

const targetPath = path.join(
targetDir,
Expand Down
4 changes: 4 additions & 0 deletions src/utils/consts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,7 @@ export const SOLIDITY_FRAMEWORKS = {
HARDHAT: "hardhat",
FOUNDRY: "foundry",
} as const;

export const GLOBAL_ARGS_DEFAULTS = {
solidityFramework: "",
};
6 changes: 3 additions & 3 deletions templates/base/.cursor/rules/scaffold-eth.mdc.template.mjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { withDefaults } from "../../../utils.js"
import { withDefaults, upperCaseFirstLetter } from "../../../utils.js"

const contents = ({ solidityFramework, deployScriptDir }) => {
return `---
Expand All @@ -11,7 +11,7 @@ This codebase contains Scaffold-ETH 2 (SE-2), everything you need to build dApps

It's a yarn monorepo that contains following packages:

${Boolean(solidityFramework[0]) ? `- ${solidityFramework[0].toUpperCase()} (\`packages/${solidityFramework[0]}\`): The solidity framework to write, test and deploy EVM Smart Contracts.` : ""}
${Boolean(solidityFramework[0]) ? `- ${upperCaseFirstLetter(solidityFramework[0])} (\`packages/${solidityFramework[0]}\`): The solidity framework to write, test and deploy EVM Smart Contracts.` : ""}
- NextJS (\`packages/nextjs\`): The UI framework extended with utilities to make interacting with Smart Contracts easy (using Next.js App Router, not Pages Router).


Expand Down Expand Up @@ -115,4 +115,4 @@ They live under \`packages/nextjs/components/scaffold-eth\`.
Find the relevant information from the documentation and the codebase. Think step by step before answering the question.`
}

export default withDefaults(contents, { solidityFramework: "", deployScriptDir: "" });
export default withDefaults(contents, { deployScriptDir: "" });
7 changes: 3 additions & 4 deletions templates/base/README.md.template.mjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { withDefaults } from "../utils.js";
import { upperCaseFirstLetter, withDefaults } from "../utils.js";

const getQuickStart = ({
solidityFramework,
Expand Down Expand Up @@ -26,7 +26,7 @@ ${
yarn chain
\`\`\`

This command starts a local Ethereum network using ${solidityFramework[0]}. The network runs on your local machine and can be used for testing and development. You can customize the network configuration in ${networkConfigPath[0]}.
This command starts a local Ethereum network using ${upperCaseFirstLetter(solidityFramework[0])}. The network runs on your local machine and can be used for testing and development. You can customize the network configuration in ${networkConfigPath[0]}.

3. On a second terminal, deploy the test contract:

Expand Down Expand Up @@ -84,7 +84,7 @@ const contents = ({
🧪 An open-source, up-to-date toolkit for building decentralized applications (dapps) on the Ethereum blockchain. It's designed to make it easier for developers to create and deploy smart contracts and build user interfaces that interact with those contracts.

⚙️ Built using NextJS, RainbowKit, ${
Boolean(solidityFramework[0]) ? solidityFramework[0] + ", " : ""
Boolean(solidityFramework[0]) ? upperCaseFirstLetter(solidityFramework[0]) + ", " : ""
}Wagmi, Viem, and Typescript.
${
Boolean(solidityFramework[0])
Expand Down Expand Up @@ -128,7 +128,6 @@ Please see [CONTRIBUTING.MD](https://github.com/scaffold-eth/scaffold-eth-2/blob

export default withDefaults(contents, {
skipQuickStart: false,
solidityFramework: "",
networkConfigPath: "",
contractsPath: "",
scriptsPath: "",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
export const solidityFramework = "foundry";
export const deployScriptDir = "script";

1 change: 0 additions & 1 deletion templates/solidity-frameworks/foundry/README.md.args.mjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
export const solidityFramework = "Foundry";
export const networkConfigPath = `\`packages/foundry/foundry.toml\``;
export const contractsPath = `\`packages/foundry/contracts\``;
export const scriptsPath = `\`packages/foundry/script\``;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
export const solidityFramework = "hardhat";
export const deployScriptDir = "deploy";

1 change: 0 additions & 1 deletion templates/solidity-frameworks/hardhat/README.md.args.mjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
export const solidityFramework = "Hardhat";
export const networkConfigPath = `\`packages/hardhat/hardhat.config.ts\``;
export const contractsPath = `\`packages/hardhat/contracts\``;
export const scriptsPath = `\`packages/hardhat/deploy\``;
Expand Down
37 changes: 27 additions & 10 deletions templates/utils.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { inspect } from "util";
import createDeepMerge from "@fastify/deepmerge";

export const upperCaseFirstLetter = (str) => str.charAt(0).toUpperCase() + str.slice(1);

const getType = (value) => {
if (Array.isArray(value)) {
return 'array';
Expand Down Expand Up @@ -57,23 +59,35 @@ export const deepMerge = (...args) => {
return finalConfig;
};

// copy of the defaults from the src/utils/consts.ts file
const GLOBAL_ARGS_DEFAULTS = {
solidityFramework: "",
};

export const withDefaults =
(template, expectedArgsDefaults, debug = false) => {
const callerFile = getCallerFile();

return receivedArgs => {
const argsWithDefault = Object.fromEntries(
Object.entries(expectedArgsDefaults).map(([argName, argDefault]) => [
argName,
receivedArgs[argName] ?? [argDefault],
]),
const globalArgsNames = Object.keys(GLOBAL_ARGS_DEFAULTS);
const receivedGlobalArgs = globalArgsNames.reduce((acc, arg) => {
acc[arg] = receivedArgs[arg][0]; return acc;}, {});

const argsWithDefaultsAndGlobals = Object.fromEntries(
Object.entries({...expectedArgsDefaults, ...receivedGlobalArgs}).map(([argName, argDefault]) => {
const receivedArg = receivedArgs[argName] ?? [argDefault];
if (receivedArg[0] instanceof Function) {
return [argName, [receivedArg[0](receivedGlobalArgs)]];
}
return [argName, receivedArg];
}),
);

if (debug) {
console.log(argsWithDefault, expectedArgsDefaults, receivedArgs);
console.log(argsWithDefaultsAndGlobals, expectedArgsDefaults, receivedArgs);
}

const expectedArgsNames = Object.keys(expectedArgsDefaults);
const expectedArgsNames = Object.keys(argsWithDefaultsAndGlobals);
Object.keys(receivedArgs).forEach(receivedArgName => {
if (!expectedArgsNames.includes(receivedArgName)) {
throw new Error(
Expand All @@ -83,8 +97,11 @@ export const withDefaults =
);
}

const receivedType = getType(receivedArgs[receivedArgName][0]);
const expectedType = getType(expectedArgsDefaults[receivedArgName]);
let receivedType = getType(receivedArgs[receivedArgName][0]);
if (receivedType === "function") {
receivedType = getType(receivedArgs[receivedArgName][0]({receivedGlobalArgs}));
}
const expectedType = getType(argsWithDefaultsAndGlobals[receivedArgName][0]);

if (receivedType !== expectedType) {
throw new Error(
Expand All @@ -95,7 +112,7 @@ export const withDefaults =
}
});

return template(argsWithDefault);
return template(argsWithDefaultsAndGlobals);
};
};

Expand Down