diff --git a/contributors/TEMPLATING.md b/contributors/TEMPLATING.md index 3de4ae1614..7606992634 100644 --- a/contributors/TEMPLATING.md +++ b/contributors/TEMPLATING.md @@ -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. diff --git a/src/tasks/copy-template-files.ts b/src/tasks/copy-template-files.ts index df2dc35679..ff205a0ce0 100644 --- a/src/tasks/copy-template-files.ts +++ b/src/tasks/copy-template-files.ts @@ -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"; @@ -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, diff --git a/src/utils/consts.ts b/src/utils/consts.ts index b6a942488d..3c8a7401a4 100644 --- a/src/utils/consts.ts +++ b/src/utils/consts.ts @@ -6,3 +6,7 @@ export const SOLIDITY_FRAMEWORKS = { HARDHAT: "hardhat", FOUNDRY: "foundry", } as const; + +export const GLOBAL_ARGS_DEFAULTS = { + solidityFramework: "", +}; diff --git a/templates/base/.cursor/rules/scaffold-eth.mdc.template.mjs b/templates/base/.cursor/rules/scaffold-eth.mdc.template.mjs index 0136ba6167..d0602bafd4 100644 --- a/templates/base/.cursor/rules/scaffold-eth.mdc.template.mjs +++ b/templates/base/.cursor/rules/scaffold-eth.mdc.template.mjs @@ -1,4 +1,4 @@ -import { withDefaults } from "../../../utils.js" +import { withDefaults, upperCaseFirstLetter } from "../../../utils.js" const contents = ({ solidityFramework, deployScriptDir }) => { return `--- @@ -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). @@ -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: "" }); diff --git a/templates/base/README.md.template.mjs b/templates/base/README.md.template.mjs index 33d616ac64..b4c2d180e9 100644 --- a/templates/base/README.md.template.mjs +++ b/templates/base/README.md.template.mjs @@ -1,4 +1,4 @@ -import { withDefaults } from "../utils.js"; +import { upperCaseFirstLetter, withDefaults } from "../utils.js"; const getQuickStart = ({ solidityFramework, @@ -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: @@ -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]) @@ -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: "", diff --git a/templates/solidity-frameworks/foundry/.cursor/rules/scaffold-eth.mdc.args.mjs b/templates/solidity-frameworks/foundry/.cursor/rules/scaffold-eth.mdc.args.mjs index 3313fd0edb..41c7de4cc5 100644 --- a/templates/solidity-frameworks/foundry/.cursor/rules/scaffold-eth.mdc.args.mjs +++ b/templates/solidity-frameworks/foundry/.cursor/rules/scaffold-eth.mdc.args.mjs @@ -1,3 +1,2 @@ -export const solidityFramework = "foundry"; export const deployScriptDir = "script"; diff --git a/templates/solidity-frameworks/foundry/README.md.args.mjs b/templates/solidity-frameworks/foundry/README.md.args.mjs index 750d91ae8e..d2cedcf930 100644 --- a/templates/solidity-frameworks/foundry/README.md.args.mjs +++ b/templates/solidity-frameworks/foundry/README.md.args.mjs @@ -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\``; diff --git a/templates/solidity-frameworks/hardhat/.cursor/rules/scaffold-eth.mdc.args.mjs b/templates/solidity-frameworks/hardhat/.cursor/rules/scaffold-eth.mdc.args.mjs index 88ebcf7e1a..1da74e3606 100644 --- a/templates/solidity-frameworks/hardhat/.cursor/rules/scaffold-eth.mdc.args.mjs +++ b/templates/solidity-frameworks/hardhat/.cursor/rules/scaffold-eth.mdc.args.mjs @@ -1,3 +1,2 @@ -export const solidityFramework = "hardhat"; export const deployScriptDir = "deploy"; diff --git a/templates/solidity-frameworks/hardhat/README.md.args.mjs b/templates/solidity-frameworks/hardhat/README.md.args.mjs index a390c43e46..872a5c59d8 100644 --- a/templates/solidity-frameworks/hardhat/README.md.args.mjs +++ b/templates/solidity-frameworks/hardhat/README.md.args.mjs @@ -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\``; diff --git a/templates/utils.js b/templates/utils.js index e4f7b354ef..cdd1c4baf4 100644 --- a/templates/utils.js +++ b/templates/utils.js @@ -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'; @@ -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( @@ -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( @@ -95,7 +112,7 @@ export const withDefaults = } }); - return template(argsWithDefault); + return template(argsWithDefaultsAndGlobals); }; };