create-dynamic-app
Advanced tools
Comparing version 2.0.1 to 2.0.3
#!/usr/bin/env node | ||
import inquirer from 'inquirer'; | ||
import { simpleGit } from 'simple-git'; | ||
import chalk from 'chalk'; | ||
import figlet from 'figlet'; | ||
import { promises as fs } from 'fs'; | ||
import { generateNextApp } from './generate-next.js'; | ||
import { generateReactApp } from './generate-react.js'; | ||
import { promptForChains } from './utils.js'; | ||
import inquirer from "inquirer"; | ||
import { simpleGit } from "simple-git"; | ||
import chalk from "chalk"; | ||
import figlet from "figlet"; | ||
import { promises as fs } from "fs"; | ||
import { generateNextApp } from "./generate-next.js"; | ||
import { generateReactApp } from "./generate-react.js"; | ||
import { promptForChains } from "./utils.js"; | ||
console.log(chalk.yellow(figlet.textSync('Create Dynamic App', { horizontalLayout: 'full' }))); | ||
console.log( | ||
chalk.yellow( | ||
figlet.textSync("Create Dynamic App", { horizontalLayout: "full" }), | ||
), | ||
); | ||
console.log(chalk.green('Welcome to the Dynamic App Creator! Follow the prompts to set up your project.')); | ||
console.log( | ||
chalk.green( | ||
"Welcome to the Dynamic App Creator! Follow the prompts to set up your project.", | ||
), | ||
); | ||
@@ -19,12 +27,12 @@ async function askFrameworkQuestion() { | ||
{ | ||
type: 'list', | ||
name: 'framework', | ||
message: 'What framework would you like to use?', | ||
type: "list", | ||
name: "framework", | ||
message: "What framework would you like to use?", | ||
choices: [ | ||
{ name: 'NextJS', value: 'nextjs' }, | ||
{ name: 'ReactJS', value: 'react' }, | ||
{ name: 'React Native', value: 'react-native' }, | ||
{ name: 'Scaffold Eth Hacker Edition', value: 'scaffold-eth' } | ||
{ name: "NextJS", value: "nextjs" }, | ||
{ name: "ReactJS", value: "react" }, | ||
{ name: "React Native", value: "react-native" }, | ||
{ name: "Scaffold Eth Hacker Edition", value: "scaffold-eth" }, | ||
], | ||
} | ||
}, | ||
]; | ||
@@ -37,10 +45,11 @@ return inquirer.prompt(frameworkQuestion); | ||
{ | ||
type: 'list', | ||
name: 'library', | ||
message: 'For Ethereum interactions, are you happy with Viem, or do you need Ethers?', | ||
type: "list", | ||
name: "library", | ||
message: | ||
"For Ethereum interactions, are you happy with Viem, or do you need Ethers?", | ||
choices: [ | ||
{ name: 'Happy with Viem', value: 'viem' }, | ||
{ name: 'I need Ethers', value: 'ethers' } | ||
] | ||
} | ||
{ name: "Happy with Viem", value: "viem" }, | ||
{ name: "I need Ethers", value: "ethers" }, | ||
], | ||
}, | ||
]; | ||
@@ -53,10 +62,10 @@ return inquirer.prompt(libraryQuestion); | ||
{ | ||
type: 'list', | ||
name: 'wagmi', | ||
message: 'Do you want to use Wagmi on top of Viem?', | ||
type: "list", | ||
name: "wagmi", | ||
message: "Do you want to use Wagmi on top of Viem?", | ||
choices: [ | ||
{ name: 'Yes', value: 'wagmi' }, | ||
{ name: 'No', value: '' } | ||
] | ||
} | ||
{ name: "Yes", value: "wagmi" }, | ||
{ name: "No", value: "" }, | ||
], | ||
}, | ||
]; | ||
@@ -76,25 +85,39 @@ return inquirer.prompt(wagmiQuestion); | ||
async function generateApp(repoUrl, directoryName, answers, selectedChains) { | ||
console.log(answers); | ||
if (answers.framework === 'nextjs' || answers.framework === 'react') { | ||
console.log('answers',answers) | ||
const useViem = answers.library === 'viem'; | ||
const useWagmi = answers.wagmi === 'wagmi'; | ||
if (answers.framework === "nextjs" || answers.framework === "react") { | ||
const useViem = answers.library === "viem"; | ||
const useWagmi = answers.wagmi === "wagmi"; | ||
if (answers.framework === 'nextjs') { | ||
await generateNextApp(process.cwd(), directoryName, useViem, useWagmi, selectedChains); | ||
} else if (answers.framework === 'react') { | ||
await generateReactApp(process.cwd(), directoryName, useViem, useWagmi, selectedChains); | ||
if (answers.framework === "nextjs") { | ||
await generateNextApp( | ||
process.cwd(), | ||
directoryName, | ||
useViem, | ||
useWagmi, | ||
selectedChains, | ||
); | ||
} else if (answers.framework === "react") { | ||
await generateReactApp( | ||
process.cwd(), | ||
directoryName, | ||
useViem, | ||
useWagmi, | ||
selectedChains, | ||
); | ||
} | ||
console.log(chalk.green(`Project setup complete! Check it out in the ${directoryName} directory.`)); | ||
console.log( | ||
chalk.green( | ||
`Project setup complete! Check it out in the ${directoryName} directory.`, | ||
), | ||
); | ||
} else { | ||
let framework; | ||
if (repoUrl === 'https://github.com/dynamic-labs/hackathon-starter-kit') { | ||
framework = 'yarn'; | ||
if (repoUrl === "https://github.com/dynamic-labs/hackathon-starter-kit") { | ||
framework = "yarn"; | ||
} else { | ||
framework = 'npm'; | ||
framework = "npm"; | ||
} | ||
const generateMessage = (directoryName) => { | ||
if (repoUrl === 'https://github.com/dynamic-labs/hackathon-starter-kit') { | ||
if (repoUrl === "https://github.com/dynamic-labs/hackathon-starter-kit") { | ||
return `Project setup complete! Cd into the ${directoryName} directory and run yarn install.`; | ||
@@ -110,7 +133,7 @@ } else { | ||
const repoGit = simpleGit(directoryName); | ||
await repoGit.removeRemote('origin'); | ||
await repoGit.removeRemote("origin"); | ||
console.log(chalk.green(generateMessage(directoryName))); | ||
} catch (error) { | ||
console.error(chalk.red('Failed to clone the repository:', error)); | ||
console.error(chalk.red("Failed to clone the repository:", error)); | ||
} | ||
@@ -122,6 +145,7 @@ } | ||
const answer = await inquirer.prompt({ | ||
type: 'input', | ||
name: 'newDirectory', | ||
type: "input", | ||
name: "newDirectory", | ||
message: `The directory "${originalDirectory}" already exists. Enter a new directory name:`, | ||
validate: input => input ? true : 'Please enter a valid directory name.' | ||
validate: (input) => | ||
input ? true : "Please enter a valid directory name.", | ||
}); | ||
@@ -139,6 +163,8 @@ | ||
export function generateRepoUrl(answers) { | ||
if (answers.framework === 'react-native') return `https://github.com/dynamic-labs/react-native-expo`; | ||
if (answers.framework === 'scaffold-eth') return `https://github.com/dynamic-labs/hackathon-starter-kit`; | ||
return `https://github.com/dynamic-labs/${answers.framework}-${answers.library}${answers.wagmi ? '-' + answers.wagmi : ''}`; | ||
if (answers.framework === "react-native") | ||
return `https://github.com/dynamic-labs/react-native-expo`; | ||
if (answers.framework === "scaffold-eth") | ||
return `https://github.com/dynamic-labs/hackathon-starter-kit`; | ||
return `https://github.com/dynamic-labs/${answers.framework}-${answers.library}${answers.wagmi ? "-" + answers.wagmi : ""}`; | ||
} | ||
@@ -149,5 +175,5 @@ | ||
const selectedChains = await promptForChains(); | ||
const hasEthereum = selectedChains.some(chain => chain.name === 'Ethereum'); | ||
const hasEthereum = selectedChains.some((chain) => chain.name === "Ethereum"); | ||
let answers = { ...initialAnswers, library: '', wagmi: '' }; | ||
let answers = { ...initialAnswers, library: "", wagmi: "" }; | ||
@@ -158,3 +184,3 @@ if (hasEthereum) { | ||
if (answers.library === 'viem') { | ||
if (answers.library === "viem") { | ||
const wagmiAnswer = await askWagmiQuestion(); | ||
@@ -168,6 +194,6 @@ answers.wagmi = wagmiAnswer.wagmi; | ||
if (answers.framework === 'scaffold-eth') { | ||
directoryName = process.argv[2] || 'my-hacker-project'; | ||
if (answers.framework === "scaffold-eth") { | ||
directoryName = process.argv[2] || "my-hacker-project"; | ||
} else { | ||
directoryName = process.argv[2] || 'my-dynamic-project'; | ||
directoryName = process.argv[2] || "my-dynamic-project"; | ||
} | ||
@@ -179,13 +205,22 @@ | ||
directoryName = await askForNewDirectory(directoryName, repoUrl); | ||
} | ||
} | ||
await generateApp(repoUrl, directoryName, answers, selectedChains); | ||
if (answers.framework === 'scaffold-eth') { | ||
if (answers.framework === "scaffold-eth") { | ||
const url = "https://github.com/dynamic-labs/hackathon-starter-kit"; | ||
console.log(chalk.magenta('Check out the documentation for the Scaffold Eth Hacker Edition at ') + url + chalk.magenta(' to get started!')); | ||
console.log( | ||
chalk.magenta( | ||
"Check out the documentation for the Scaffold Eth Hacker Edition at ", | ||
) + | ||
url + | ||
chalk.magenta(" to get started!"), | ||
); | ||
} else { | ||
const url = "https://app.dynamic.xyz/dashboard/developer/api"; | ||
console.log(chalk.magenta('Make sure to grab your Environment ID from ') + url + chalk.magenta(' and add it to the DynamicContextProvider!')); | ||
console.log( | ||
chalk.magenta("Make sure to grab your Environment ID from ") + | ||
url + | ||
chalk.magenta(" and add it to the DynamicContextProvider!"), | ||
); | ||
} | ||
@@ -192,0 +227,0 @@ } |
@@ -1,19 +0,41 @@ | ||
import fs from 'fs/promises'; | ||
import path from 'path'; | ||
import { execSync } from 'child_process'; | ||
import figlet from 'figlet'; | ||
import { generateLayoutJsContent, pageJs, PageCss, generateProvidersContent, generateDynamicLibContent, wagmiContent } from './next-templates/index.js'; | ||
import { addSolanaDependencies, createConfigOverrides } from './next-templates/solana-config.js'; | ||
import { promptForChains, checkExistingDirectories } from './utils.js'; | ||
import chalk from 'chalk'; | ||
import fs from "fs/promises"; | ||
import path from "path"; | ||
import { execSync } from "child_process"; | ||
import figlet from "figlet"; | ||
import { | ||
generateLayoutJsContent, | ||
pageJs, | ||
PageCss, | ||
generateProvidersContent, | ||
generateDynamicLibContent, | ||
generateMethodsContent, | ||
MethodsCss, | ||
wagmiContent, | ||
} from "./next-templates/index.js"; | ||
import { | ||
addSolanaDependencies, | ||
createConfigOverrides, | ||
} from "./next-templates/solana-config.js"; | ||
import { promptForChains, checkExistingDirectories } from "./utils.js"; | ||
import chalk from "chalk"; | ||
import fetch from 'node-fetch'; | ||
export const generateNextApp = async (parentDir, appName, useViem, useWagmi, selectedChains) => { | ||
export const generateNextApp = async ( | ||
parentDir, | ||
appName, | ||
useViem, | ||
useWagmi, | ||
selectedChains, | ||
) => { | ||
const baseDir = path.join(parentDir, appName); | ||
const hasSolana = selectedChains.some(chain => chain.name === 'Solana'); | ||
const hasSolana = selectedChains.some((chain) => chain.name === "Solana"); | ||
console.log(chalk.blue(`Creating Next.js app: ${appName}`)); | ||
execSync(`npx create-next-app@latest ${baseDir} --eslint --typescript --app --no-src-dir --import-alias "@/*" --no-tailwind`, { stdio: 'inherit' }); | ||
execSync( | ||
`npx create-next-app@latest ${baseDir} --eslint --typescript --app --no-src-dir --import-alias "@/*" --no-tailwind`, | ||
{ stdio: "inherit" }, | ||
); | ||
const packageJsonPath = path.join(baseDir, 'package.json'); | ||
let packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8')); | ||
const packageJsonPath = path.join(baseDir, "package.json"); | ||
let packageJson = JSON.parse(await fs.readFile(packageJsonPath, "utf8")); | ||
@@ -23,9 +45,15 @@ packageJson.dependencies = { | ||
"@dynamic-labs/sdk-react-core": "^3.0.0-alpha", | ||
...(useViem && selectedChains.some(chain => chain.name === 'Ethereum') ? { viem: "*" } : !useViem && selectedChains.some(chain => chain.name === 'Ethereum') ?{ "@dynamic-labs/ethers-v6": "^3.0.0-alpha" } : {}), | ||
...(useViem && selectedChains.some((chain) => chain.name === "Ethereum") | ||
? { viem: "*" } | ||
: !useViem && selectedChains.some((chain) => chain.name === "Ethereum") | ||
? { "@dynamic-labs/ethers-v6": "^3.0.0-alpha" } | ||
: {}), | ||
...(useWagmi && { | ||
"@dynamic-labs/wagmi-connector": "^3.0.0-alpha", | ||
"@tanstack/react-query": "^5.27.5", | ||
wagmi: useViem ? "^2.5.7" : "*" | ||
wagmi: useViem ? "^2.5.7" : "*", | ||
}), | ||
...Object.fromEntries(selectedChains.map(chain => [chain.package, "^3.0.0-alpha"])) | ||
...Object.fromEntries( | ||
selectedChains.map((chain) => [chain.package, "^3.0.0-alpha"]), | ||
), | ||
}; | ||
@@ -38,19 +66,41 @@ | ||
await fs.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2)); | ||
const libDir = path.join(baseDir, 'lib'); | ||
const libDir = path.join(baseDir, "lib"); | ||
await fs.mkdir(libDir, { recursive: true }); | ||
const componentsDir = path.join(baseDir, "app/components"); | ||
await fs.mkdir(componentsDir, { recursive: true }); | ||
const filesToCreate = [ | ||
{ path: path.join(baseDir, 'app', 'layout.tsx'), content: generateLayoutJsContent(useViem, useWagmi, selectedChains) }, | ||
{ path: path.join(baseDir, 'app', 'page.tsx'), content: pageJs }, | ||
{ path: path.join(baseDir, 'app', 'page.css'), content: PageCss }, | ||
{ path: path.join(baseDir, 'lib', 'providers.tsx'), content: generateProvidersContent(useWagmi, selectedChains) }, | ||
{ path: path.join(baseDir, 'lib', 'dynamic.ts'), content: generateDynamicLibContent(useViem, useWagmi, selectedChains) }, | ||
...(useWagmi ? [{ path: path.join(baseDir, 'lib', 'wagmi.ts'), content: wagmiContent }] : []) | ||
{ | ||
path: path.join(baseDir, "app", "layout.tsx"), | ||
content: generateLayoutJsContent(useViem, useWagmi, selectedChains), | ||
}, | ||
{ path: path.join(baseDir, "app", "page.tsx"), content: pageJs }, | ||
{ path: path.join(baseDir, "app", "page.css"), content: PageCss }, | ||
{path: path.join(baseDir, "app/components", "Methods.js"), content: generateMethodsContent(selectedChains)}, | ||
{path: path.join(baseDir, "app/components", "Methods.css"), content: MethodsCss}, | ||
{ | ||
path: path.join(baseDir, "lib", "providers.tsx"), | ||
content: generateProvidersContent(useWagmi, selectedChains), | ||
}, | ||
{ | ||
path: path.join(baseDir, "lib", "dynamic.ts"), | ||
content: generateDynamicLibContent(useViem, useWagmi, selectedChains), | ||
}, | ||
...(useWagmi | ||
? [{ path: path.join(baseDir, "lib", "wagmi.ts"), content: wagmiContent }] | ||
: []), | ||
]; | ||
await Promise.all(filesToCreate.map(file => fs.writeFile(file.path, file.content))); | ||
await Promise.all( | ||
filesToCreate.map((file) => fs.writeFile(file.path, file.content)), | ||
); | ||
if (hasSolana) { | ||
await fs.writeFile(path.join(baseDir, 'next.config.js'), createConfigOverrides()); | ||
await fs.writeFile( | ||
path.join(baseDir, "next.config.js"), | ||
createConfigOverrides(), | ||
); | ||
} | ||
@@ -64,5 +114,12 @@ | ||
const copyAssets = async (baseDir) => { | ||
const assetsDir = path.join(process.cwd(), 'assets'); | ||
const publicDir = path.join(baseDir, 'public'); | ||
const publicDir = path.join(baseDir, "public"); | ||
// Hardcoded remote URLs for assets | ||
const assetUrls = [ | ||
{name: "image-light", url: 'https://cdn.prod.website-files.com/626692727bba3f384e008e8a/66e1b9f35ab65dff341d8266_image-light.png'}, | ||
{name: "image-dark", url: 'https://cdn.prod.website-files.com/626692727bba3f384e008e8a/66e1b9f31261f4025001cbff_image-dark.png'}, | ||
{name: "logo-light", url: 'https://cdn.prod.website-files.com/626692727bba3f384e008e8a/66e1b9f3915ce2792a3677b1_logo-light.png'}, | ||
{name: "logo-dark", url: 'https://cdn.prod.website-files.com/626692727bba3f384e008e8a/66e1b9e7144147903a39ab97_logo-dark.png'} | ||
]; | ||
try { | ||
@@ -72,8 +129,16 @@ // Ensure the public directory exists | ||
const files = await fs.readdir(assetsDir); | ||
await Promise.all(files.map(file => | ||
fs.copyFile(path.join(assetsDir, file), path.join(publicDir, file)) | ||
)); | ||
// Download and save each asset | ||
await Promise.all(assetUrls.map(async (asset) => { | ||
const response = await fetch(asset.url); | ||
if (response.ok) { | ||
const fileName = `${asset.name}.png`; | ||
const filePath = path.join(publicDir, fileName); | ||
const arrayBuffer = await response.arrayBuffer(); | ||
await fs.writeFile(filePath, Buffer.from(arrayBuffer)); | ||
} | ||
})); | ||
console.log(chalk.green("Assets copied successfully from remote URLs.")); | ||
} catch (error) { | ||
console.log(chalk.red('Assets folder not found or error copying assets:', error.message)); | ||
console.error(chalk.red(`Error copying assets from remote URLs: ${error.message}`)); | ||
} | ||
@@ -84,4 +149,4 @@ }; | ||
const customDir = process.argv[2]; | ||
const nextDir = customDir ? process.cwd() : path.join(process.cwd(), 'next'); | ||
const nextDir = customDir ? process.cwd() : path.join(process.cwd(), "next"); | ||
if (!customDir) { | ||
@@ -91,7 +156,9 @@ await fs.mkdir(nextDir, { recursive: true }); | ||
console.log(chalk.cyan(figlet.textSync('Next.js Apps', { horizontalLayout: 'full' }))); | ||
console.log( | ||
chalk.cyan(figlet.textSync("Next.js Apps", { horizontalLayout: "full" })), | ||
); | ||
// Ask for chains first | ||
const selectedChains = await promptForChains(); | ||
const hasEthereum = selectedChains.some(chain => chain.name === 'Ethereum'); | ||
const hasEthereum = selectedChains.some((chain) => chain.name === "Ethereum"); | ||
@@ -101,9 +168,9 @@ let appConfigs; | ||
appConfigs = [ | ||
{ name: 'next-app-viem', useViem: true, useWagmi: false }, | ||
{ name: 'next-app-viem-wagmi', useViem: true, useWagmi: true }, | ||
{ name: 'next-app-ethers', useViem: false, useWagmi: false }, | ||
{ name: "next-app-viem", useViem: true, useWagmi: false }, | ||
{ name: "next-app-viem-wagmi", useViem: true, useWagmi: true }, | ||
{ name: "next-app-ethers", useViem: false, useWagmi: false }, | ||
]; | ||
} else { | ||
appConfigs = [ | ||
{ name: 'next-app-vanilla', useViem: false, useWagmi: false }, | ||
{ name: "next-app-vanilla", useViem: false, useWagmi: false }, | ||
]; | ||
@@ -113,19 +180,36 @@ } | ||
if (!customDir) { | ||
await checkExistingDirectories(appConfigs.map(config => config.name), nextDir); | ||
await checkExistingDirectories( | ||
appConfigs.map((config) => config.name), | ||
nextDir, | ||
); | ||
} | ||
console.log(chalk.yellow(`\n🚀 About to generate ${appConfigs.length} example app${appConfigs.length > 1 ? 's' : ''}:\n`)); | ||
appConfigs.forEach(config => { | ||
const nextIcon = '▲ '; // Next.js icon (triangle) | ||
const viemIcon = config.useViem ? '🔷 ' : '📘 '; // Viem icon (blue diamond) or Ethers icon (blue book) | ||
const wagmiIcon = config.useWagmi ? '🔗 ' : ''; // Wagmi icon (chain link) | ||
console.log(chalk.green(`${nextIcon}${viemIcon}${config.name} ${wagmiIcon}`)); | ||
console.log( | ||
chalk.yellow( | ||
`\n🚀 About to generate ${appConfigs.length} example app${appConfigs.length > 1 ? "s" : ""}:\n`, | ||
), | ||
); | ||
appConfigs.forEach((config) => { | ||
const nextIcon = "▲ "; // Next.js icon (triangle) | ||
const viemIcon = config.useViem ? "🔷 " : "📘 "; // Viem icon (blue diamond) or Ethers icon (blue book) | ||
const wagmiIcon = config.useWagmi ? "🔗 " : ""; // Wagmi icon (chain link) | ||
console.log( | ||
chalk.green(`${nextIcon}${viemIcon}${config.name} ${wagmiIcon}`), | ||
); | ||
}); | ||
console.log(chalk.yellow('\nGenerating apps... 🛠️\n')); | ||
console.log(chalk.yellow("\nGenerating apps... 🛠️\n")); | ||
await Promise.all(appConfigs.map(config => | ||
generateNextApp(nextDir, config.name, config.useViem, config.useWagmi, selectedChains) | ||
)); | ||
await Promise.all( | ||
appConfigs.map((config) => | ||
generateNextApp( | ||
nextDir, | ||
config.name, | ||
config.useViem, | ||
config.useWagmi, | ||
selectedChains, | ||
), | ||
), | ||
); | ||
@@ -135,11 +219,27 @@ if (customDir) { | ||
} else { | ||
const createdApps = (await Promise.all(appConfigs.map(async config => | ||
(await fs.stat(path.join(nextDir, config.name)).catch(() => null)) ? config.name : null | ||
))).filter(Boolean); | ||
const createdApps = ( | ||
await Promise.all( | ||
appConfigs.map(async (config) => | ||
(await fs.stat(path.join(nextDir, config.name)).catch(() => null)) | ||
? config.name | ||
: null, | ||
), | ||
) | ||
).filter(Boolean); | ||
if (createdApps.length === appConfigs.length) { | ||
console.log(chalk.green('All example apps were created successfully in the "next" directory.')); | ||
console.log( | ||
chalk.green( | ||
'All example apps were created successfully in the "next" directory.', | ||
), | ||
); | ||
} else { | ||
const missingApps = appConfigs.map(config => config.name).filter(name => !createdApps.includes(name)); | ||
console.error(chalk.red(`Error: The following apps were not created: ${missingApps.join(', ')}`)); | ||
const missingApps = appConfigs | ||
.map((config) => config.name) | ||
.filter((name) => !createdApps.includes(name)); | ||
console.error( | ||
chalk.red( | ||
`Error: The following apps were not created: ${missingApps.join(", ")}`, | ||
), | ||
); | ||
} | ||
@@ -151,2 +251,2 @@ } | ||
main().catch(console.error); | ||
} | ||
} |
@@ -1,34 +0,51 @@ | ||
import fs from 'fs/promises'; | ||
import path from 'path'; | ||
import { execSync } from 'child_process'; | ||
import figlet from 'figlet'; | ||
import mainCss from './react-templates/mainCss.js'; | ||
import mainJs from './react-templates/main.js'; | ||
import generateAppJsContent from './react-templates/app.js'; | ||
import { addSolanaDependencies, createConfigOverrides, updatePackageJsonScripts } from './react-templates/solana-config.js'; | ||
import { promptForChains, checkExistingDirectories } from './utils.js'; | ||
import chalk from 'chalk'; | ||
import fs from "fs/promises"; | ||
import path from "path"; | ||
import { execSync } from "child_process"; | ||
import figlet from "figlet"; | ||
import mainCss from "./react-templates/mainCss.js"; | ||
import mainJs from "./react-templates/main.js"; | ||
import generateAppJsContent from "./react-templates/app.js"; | ||
import generateMethodsContent from "./react-templates/methods.js"; | ||
import methodsCss from "./react-templates/methodsCss.js"; | ||
import { | ||
addSolanaDependencies, | ||
createConfigOverrides, | ||
updatePackageJsonScripts, | ||
} from "./react-templates/solana-config.js"; | ||
import { promptForChains, checkExistingDirectories } from "./utils.js"; | ||
import chalk from "chalk"; | ||
import fetch from 'node-fetch'; | ||
export const generateReactApp = async (parentDir, appName, useViem, useWagmi, selectedChains) => { | ||
export const generateReactApp = async ( | ||
parentDir, | ||
appName, | ||
useViem, | ||
useWagmi, | ||
selectedChains, | ||
) => { | ||
const baseDir = path.join(parentDir, appName); | ||
const hasSolana = selectedChains.some(chain => chain.name === 'Solana'); | ||
const hasSolana = selectedChains.some((chain) => chain.name === "Solana"); | ||
const hasEthereum = selectedChains.some((chain) => chain.name === "Ethereum"); | ||
console.log(chalk.blue(`Creating React app: ${appName}`)); | ||
execSync(`npx create-react-app ${baseDir}`); | ||
const packageJsonPath = path.join(baseDir, 'package.json'); | ||
let packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8')); | ||
const packageJsonPath = path.join(baseDir, "package.json"); | ||
let packageJson = JSON.parse(await fs.readFile(packageJsonPath, "utf8")); | ||
packageJson.dependencies = { | ||
...packageJson.dependencies, | ||
"@dynamic-labs/sdk-react-core": "^3.0.0-alpha", | ||
"react": "^18.2.0", | ||
"react-dom": "^18.2.0", | ||
"react-scripts": "5.0.1", | ||
...(useViem && selectedChains.some(chain => chain.name === 'Ethereum') ? { viem: "*" } : !useViem && selectedChains.some(chain => chain.name === 'Ethereum') ?{ "@dynamic-labs/ethers-v6": "^3.0.0-alpha" } : {}), | ||
...(useViem && hasEthereum | ||
? { viem: "*" } | ||
: !useViem && hasEthereum | ||
? { "@dynamic-labs/ethers-v6": "^3.0.0-alpha" } | ||
: {}), | ||
...(useWagmi && { | ||
"@dynamic-labs/wagmi-connector": "^3.0.0-alpha", | ||
"@tanstack/react-query": "^5.27.5", | ||
wagmi: useViem ? "^2.5.7" : "*" | ||
wagmi: useViem ? "^2.5.7" : "*", | ||
}), | ||
...Object.fromEntries(selectedChains.map(chain => [chain.package, "^3.0.0-alpha"])) | ||
...Object.fromEntries( | ||
selectedChains.map((chain) => [chain.package, "^3.0.0-alpha"]), | ||
), | ||
}; | ||
@@ -42,3 +59,3 @@ | ||
...packageJson.devDependencies, | ||
"web-vitals": "^2.1.4" | ||
"web-vitals": "^2.1.4", | ||
}; | ||
@@ -48,3 +65,3 @@ | ||
...(packageJson.overrides ?? {}), | ||
typescript: "4.9.5" | ||
typescript: "4.9.5", | ||
}; | ||
@@ -59,9 +76,15 @@ | ||
const appJsContent = generateAppJsContent(useViem, useWagmi, selectedChains); | ||
await fs.writeFile(path.join(baseDir, 'src', 'App.js'), appJsContent); | ||
await fs.writeFile(path.join(baseDir, "src", "App.js"), appJsContent); | ||
await fs.writeFile(path.join(baseDir, 'src', 'Main.js'), mainJs); | ||
await fs.writeFile(path.join(baseDir, 'src', 'Main.css'), mainCss); | ||
await fs.writeFile(path.join(baseDir, "src", "Main.js"), mainJs); | ||
await fs.writeFile(path.join(baseDir, "src", "Main.css"), mainCss); | ||
await fs.writeFile( | ||
path.join(baseDir, "src", "Methods.js"), | ||
generateMethodsContent(selectedChains), | ||
); | ||
await fs.writeFile(path.join(baseDir, "src", "Methods.css"), methodsCss); | ||
if (hasSolana) { | ||
const configOverridesPath = path.join(baseDir, 'config-overrides.js'); | ||
const configOverridesPath = path.join(baseDir, "config-overrides.js"); | ||
const configOverridesContent = createConfigOverrides(); | ||
@@ -77,5 +100,12 @@ await fs.writeFile(configOverridesPath, configOverridesContent); | ||
const copyAssets = async (baseDir) => { | ||
const assetsDir = path.join(process.cwd(), 'assets'); | ||
const publicDir = path.join(baseDir, 'public'); | ||
const publicDir = path.join(baseDir, "public"); | ||
// Hardcoded remote URLs for assets | ||
const assetUrls = [ | ||
{name: "image-light", url: 'https://cdn.prod.website-files.com/626692727bba3f384e008e8a/66e1b9f35ab65dff341d8266_image-light.png'}, | ||
{name: "image-dark", url: 'https://cdn.prod.website-files.com/626692727bba3f384e008e8a/66e1b9f31261f4025001cbff_image-dark.png'}, | ||
{name: "logo-light", url: 'https://cdn.prod.website-files.com/626692727bba3f384e008e8a/66e1b9f3915ce2792a3677b1_logo-light.png'}, | ||
{name: "logo-dark", url: 'https://cdn.prod.website-files.com/626692727bba3f384e008e8a/66e1b9e7144147903a39ab97_logo-dark.png'} | ||
]; | ||
try { | ||
@@ -85,12 +115,16 @@ // Ensure the public directory exists | ||
const files = await fs.readdir(assetsDir); | ||
await Promise.all(files.map(file => | ||
fs.copyFile(path.join(assetsDir, file), path.join(publicDir, file)) | ||
)); | ||
// Download and save each asset | ||
await Promise.all(assetUrls.map(async (asset) => { | ||
const response = await fetch(asset.url); | ||
if (response.ok) { | ||
const fileName = `${asset.name}.png`; | ||
const filePath = path.join(publicDir, fileName); | ||
const arrayBuffer = await response.arrayBuffer(); | ||
await fs.writeFile(filePath, Buffer.from(arrayBuffer)); | ||
} | ||
})); | ||
console.log(chalk.green("Assets copied successfully from remote URLs.")); | ||
} catch (error) { | ||
if (error.code === 'ENOENT' && error.path === assetsDir) { | ||
console.log(chalk.yellow('Assets folder not found. Skipping asset copy.')); | ||
} else { | ||
console.error(chalk.red(`Error copying assets: ${error.message}`)); | ||
} | ||
console.error(chalk.red(`Error copying assets from remote URLs: ${error.message}`)); | ||
} | ||
@@ -101,4 +135,6 @@ }; | ||
const customDir = process.argv[2]; | ||
const reactDir = customDir ? process.cwd() : path.join(process.cwd(), 'react'); | ||
const reactDir = customDir | ||
? process.cwd() | ||
: path.join(process.cwd(), "react"); | ||
if (!customDir) { | ||
@@ -108,6 +144,8 @@ await fs.mkdir(reactDir, { recursive: true }); | ||
console.log(chalk.cyan(figlet.textSync('React Apps', { horizontalLayout: 'full' }))); | ||
console.log( | ||
chalk.cyan(figlet.textSync("React Apps", { horizontalLayout: "full" })), | ||
); | ||
const selectedChains = await promptForChains(); | ||
const hasEthereum = selectedChains.some(chain => chain.name === 'Ethereum'); | ||
const hasEthereum = selectedChains.some((chain) => chain.name === "Ethereum"); | ||
@@ -117,9 +155,9 @@ let appConfigs; | ||
appConfigs = [ | ||
{ name: 'react-app-viem', useViem: true, useWagmi: false }, | ||
{ name: 'react-app-viem-wagmi', useViem: true, useWagmi: true }, | ||
{ name: 'react-app-ethers', useViem: false, useWagmi: false }, | ||
{ name: "react-app-viem", useViem: true, useWagmi: false }, | ||
{ name: "react-app-viem-wagmi", useViem: true, useWagmi: true }, | ||
{ name: "react-app-ethers", useViem: false, useWagmi: false }, | ||
]; | ||
} else { | ||
appConfigs = [ | ||
{ name: 'react-app-vanilla', useViem: false, useWagmi: false }, | ||
{ name: "react-app-vanilla", useViem: false, useWagmi: false }, | ||
]; | ||
@@ -129,19 +167,40 @@ } | ||
if (!customDir) { | ||
await checkExistingDirectories(appConfigs.map(config => config.name), reactDir); | ||
await checkExistingDirectories( | ||
appConfigs.map((config) => config.name), | ||
reactDir, | ||
); | ||
} | ||
console.log(chalk.yellow(`\n🚀 About to generate ${appConfigs.length} example app${appConfigs.length > 1 ? 's' : ''}:\n`)); | ||
appConfigs.forEach(config => { | ||
const reactIcon = '⚛️ '; // React icon | ||
const viemIcon = config.useViem ? '🔷 ' : (hasEthereum && !config.useViem ? '📘 ' : ''); // Viem icon (blue diamond) or Ethers icon (blue book) if Ethereum | ||
const wagmiIcon = config.useWagmi ? '🔗 ' : ''; // Wagmi icon (chain link) | ||
console.log(chalk.green(`${reactIcon}${viemIcon}${config.name} ${wagmiIcon}`)); | ||
console.log( | ||
chalk.yellow( | ||
`\n🚀 About to generate ${appConfigs.length} example app${appConfigs.length > 1 ? "s" : ""}:\n`, | ||
), | ||
); | ||
appConfigs.forEach((config) => { | ||
const reactIcon = "⚛️ "; // React icon | ||
const viemIcon = config.useViem | ||
? "🔷 " | ||
: hasEthereum && !config.useViem | ||
? "📘 " | ||
: ""; // Viem icon (blue diamond) or Ethers icon (blue book) if Ethereum | ||
const wagmiIcon = config.useWagmi ? "🔗 " : ""; // Wagmi icon (chain link) | ||
console.log( | ||
chalk.green(`${reactIcon}${viemIcon}${config.name} ${wagmiIcon}`), | ||
); | ||
}); | ||
console.log(chalk.yellow('\nGenerating app(s)... 🛠️\n')); | ||
console.log(chalk.yellow("\nGenerating app(s)... 🛠️\n")); | ||
await Promise.all(appConfigs.map(config => | ||
generateReactApp(reactDir, config.name, config.useViem, config.useWagmi, selectedChains) | ||
)); | ||
await Promise.all( | ||
appConfigs.map((config) => | ||
generateReactApp( | ||
reactDir, | ||
config.name, | ||
config.useViem, | ||
config.useWagmi, | ||
selectedChains, | ||
), | ||
), | ||
); | ||
@@ -151,11 +210,27 @@ if (customDir) { | ||
} else { | ||
const createdApps = (await Promise.all(appConfigs.map(async config => | ||
(await fs.stat(path.join(reactDir, config.name)).catch(() => null)) ? config.name : null | ||
))).filter(Boolean); | ||
const createdApps = ( | ||
await Promise.all( | ||
appConfigs.map(async (config) => | ||
(await fs.stat(path.join(reactDir, config.name)).catch(() => null)) | ||
? config.name | ||
: null, | ||
), | ||
) | ||
).filter(Boolean); | ||
if (createdApps.length === appConfigs.length) { | ||
console.log(chalk.green('All example apps were created successfully in the "react" directory.')); | ||
console.log( | ||
chalk.green( | ||
'All example apps were created successfully in the "react" directory.', | ||
), | ||
); | ||
} else { | ||
const missingApps = appConfigs.map(config => config.name).filter(name => !createdApps.includes(name)); | ||
console.error(chalk.red(`Error: The following apps were not created: ${missingApps.join(', ')}`)); | ||
const missingApps = appConfigs | ||
.map((config) => config.name) | ||
.filter((name) => !createdApps.includes(name)); | ||
console.error( | ||
chalk.red( | ||
`Error: The following apps were not created: ${missingApps.join(", ")}`, | ||
), | ||
); | ||
} | ||
@@ -167,2 +242,2 @@ } | ||
main().catch(console.error); | ||
} | ||
} |
const generateDynamicLibContent = (useViem, useWagmi, selectedChains) => { | ||
let imports = `export * from "@dynamic-labs/sdk-react-core";\n`; | ||
if (!useViem && selectedChains.some(chain => chain.name === 'Ethereum')) { | ||
imports += `export * from "@dynamic-labs/ethers-v6";\n`; | ||
} | ||
let imports = `export * from "@dynamic-labs/sdk-react-core";\n`; | ||
return imports; | ||
if (!useViem && selectedChains.some((chain) => chain.name === "Ethereum")) { | ||
imports += `export * from "@dynamic-labs/ethers-v6";\n`; | ||
} | ||
selectedChains.forEach((chain) => { | ||
imports += `export * from "@dynamic-labs/${chain.name.toLowerCase()}";\n`; | ||
}); | ||
return imports; | ||
}; | ||
export default generateDynamicLibContent; |
@@ -1,8 +0,11 @@ | ||
import page from './page.js'; | ||
import generateLayoutContent from './layout.js'; | ||
import generateDynamicLibContent from './dynamic.js'; | ||
import generateProvidersContent from './providers.js'; | ||
import wagmiContent from './wagmi.js'; | ||
import PageCss from './pageCss.js'; | ||
import page from "./page.js"; | ||
import generateLayoutContent from "./layout.js"; | ||
import generateDynamicLibContent from "./dynamic.js"; | ||
import generateProvidersContent from "./providers.js"; | ||
import generateMethodsContent from "./methods.js"; | ||
import wagmiContent from "./wagmi.js"; | ||
import PageCss from "./pageCss.js"; | ||
import MethodsCss from "./methodsCss.js"; | ||
export { | ||
@@ -13,4 +16,6 @@ page as pageJs, | ||
generateProvidersContent, | ||
generateMethodsContent, | ||
wagmiContent, | ||
PageCss, | ||
MethodsCss, | ||
}; |
const generateLayoutContent = () => { | ||
let imports = `import "./globals.css";\n`; | ||
imports += `import type { Metadata } from "next";\n`; | ||
imports += `import { Inter } from "next/font/google";\n`; | ||
imports += `import Providers from "@/lib/providers";\n`; | ||
let content = ` | ||
let imports = `import "./globals.css";\n`; | ||
imports += `import type { Metadata } from "next";\n`; | ||
imports += `import { Inter } from "next/font/google";\n`; | ||
imports += `import Providers from "@/lib/providers";\n`; | ||
let content = ` | ||
const inter = Inter({ subsets: ["latin"] }); | ||
@@ -27,5 +27,5 @@ | ||
return `${imports}\n${content}`; | ||
return `${imports}\n${content}`; | ||
}; | ||
export default generateLayoutContent; | ||
export default generateLayoutContent; |
@@ -6,2 +6,3 @@ const page = ` | ||
import { useState, useEffect } from 'react'; | ||
import DynamicMethods from "@/app/components/Methods"; | ||
import './page.css'; | ||
@@ -38,2 +39,3 @@ | ||
<DynamicWidget /> | ||
<DynamicMethods isDarkMode={isDarkMode} /> | ||
</div> | ||
@@ -40,0 +42,0 @@ <div className="footer"> |
@@ -73,2 +73,3 @@ const pageCss = ` | ||
justify-content: center; | ||
gap: 20px; | ||
} | ||
@@ -118,4 +119,4 @@ | ||
} | ||
` | ||
`; | ||
export default pageCss; |
const generateProvidersContent = (useWagmi, selectedChains) => { | ||
let imports = `'use client';\n\n`; | ||
imports += `import { DynamicContextProvider } from "@dynamic-labs/sdk-react-core";\n`; | ||
let imports = `'use client';\n\n`; | ||
imports += `import { DynamicContextProvider } from "@dynamic-labs/sdk-react-core";\n`; | ||
selectedChains.forEach(chain => { | ||
imports += `import { ${chain.connector} } from "${chain.package}";\n`; | ||
}); | ||
if (useWagmi) { | ||
imports += `import { WagmiProvider } from "wagmi";\n`; | ||
imports += `import { QueryClient, QueryClientProvider } from "@tanstack/react-query";\n`; | ||
imports += `import { DynamicWagmiConnector } from "@dynamic-labs/wagmi-connector";\n`; | ||
imports += `import { config } from "@/lib/wagmi";\n`; | ||
} | ||
selectedChains.forEach((chain) => { | ||
imports += `import { ${chain.connector} } from "${chain.package}";\n`; | ||
}); | ||
const walletConnectors = selectedChains.map(chain => chain.connector).join(', '); | ||
if (useWagmi) { | ||
imports += `import { WagmiProvider } from "wagmi";\n`; | ||
imports += `import { QueryClient, QueryClientProvider } from "@tanstack/react-query";\n`; | ||
imports += `import { DynamicWagmiConnector } from "@dynamic-labs/wagmi-connector";\n`; | ||
imports += `import { config } from "@/lib/wagmi";\n`; | ||
} | ||
let content = ` | ||
const walletConnectors = selectedChains | ||
.map((chain) => chain.connector) | ||
.join(", "); | ||
let content = ` | ||
export default function Providers({ | ||
@@ -25,3 +27,3 @@ children, | ||
${useWagmi ? `const queryClient = new QueryClient();` : ''} | ||
${useWagmi ? `const queryClient = new QueryClient();` : ""} | ||
@@ -36,3 +38,5 @@ return ( | ||
> | ||
${useWagmi ? ` | ||
${ | ||
useWagmi | ||
? ` | ||
<WagmiProvider config={config}> | ||
@@ -45,3 +49,5 @@ <QueryClientProvider client={queryClient}> | ||
</WagmiProvider> | ||
` : '{children}'} | ||
` | ||
: "{children}" | ||
} | ||
</DynamicContextProvider> | ||
@@ -51,5 +57,5 @@ ); | ||
return `${imports}\n${content}`; | ||
} | ||
return `${imports}\n${content}`; | ||
}; | ||
export default generateProvidersContent; | ||
export default generateProvidersContent; |
export const addSolanaDependencies = (packageJson) => { | ||
packageJson.dependencies['crypto-browserify'] = "^3.12.0"; | ||
packageJson.dependencies['stream-browserify'] = "^3.0.0"; | ||
packageJson.dependencies['process'] = "^0.11.10"; | ||
packageJson.dependencies["crypto-browserify"] = "^3.12.0"; | ||
packageJson.dependencies["stream-browserify"] = "^3.0.0"; | ||
packageJson.dependencies["process"] = "^0.11.10"; | ||
// Add the browser field | ||
packageJson.browser = { | ||
...packageJson.browser, | ||
crypto: false | ||
crypto: false, | ||
}; | ||
@@ -49,2 +49,2 @@ // Add the solana overrides | ||
return packageJson; | ||
}; | ||
}; |
@@ -19,4 +19,4 @@ const wagmi = ` | ||
} | ||
` | ||
`; | ||
export default wagmi; |
{ | ||
"name": "create-dynamic-app", | ||
"version": "2.0.1", | ||
"version": "2.0.3", | ||
"scripts": { | ||
@@ -20,4 +20,8 @@ "generate-react": "node generate-react.js", | ||
"inquirer": "^10.1.8", | ||
"node-fetch": "^3.3.2", | ||
"simple-git": "^3.26.0" | ||
}, | ||
"devDependencies": { | ||
"prettier": "3.3.3" | ||
} | ||
} |
@@ -1,65 +0,70 @@ | ||
export default function generateAppJsContent(useViem, useWagmi, selectedChains) { | ||
let imports = `import { DynamicContextProvider } from "@dynamic-labs/sdk-react-core";\n`; | ||
selectedChains.forEach(chain => { | ||
imports += `import { ${chain.connector} } from "${chain.package}";\n`; | ||
}); | ||
if (useWagmi) { | ||
imports += `import { DynamicWagmiConnector } from "@dynamic-labs/wagmi-connector";\n`; | ||
imports += `import { createConfig, WagmiProvider } from "wagmi";\n`; | ||
imports += `import { QueryClient, QueryClientProvider } from "@tanstack/react-query";\n`; | ||
imports += `import { http } from "viem";\n`; | ||
imports += `import { mainnet } from "viem/chains";\n`; | ||
} | ||
imports += `import Main from "./Main";\n\n`; | ||
export default function generateAppJsContent( | ||
useViem, | ||
useWagmi, | ||
selectedChains, | ||
) { | ||
let imports = `import { DynamicContextProvider } from "@dynamic-labs/sdk-react-core";\n`; | ||
const walletConnectors = selectedChains.map(chain => chain.connector).join(', '); | ||
selectedChains.forEach((chain) => { | ||
imports += `import { ${chain.connector} } from "${chain.package}";\n`; | ||
}); | ||
let content = ''; | ||
if (useWagmi) { | ||
content = ` | ||
const config = createConfig({ | ||
chains: [mainnet], | ||
multiInjectedProviderDiscovery: false, | ||
transports: { | ||
[mainnet.id]: http(), | ||
}, | ||
}); | ||
const queryClient = new QueryClient(); | ||
const App = () => ( | ||
<DynamicContextProvider | ||
theme="auto" | ||
settings={{ | ||
environmentId: "2762a57b-faa4-41ce-9f16-abff9300e2c9", | ||
walletConnectors: [${walletConnectors}], | ||
}} | ||
> | ||
<WagmiProvider config={config}> | ||
<QueryClientProvider client={queryClient}> | ||
<DynamicWagmiConnector> | ||
<Main /> | ||
</DynamicWagmiConnector> | ||
</QueryClientProvider> | ||
</WagmiProvider> | ||
</DynamicContextProvider> | ||
);`; | ||
} else { | ||
content = ` | ||
const App = () => ( | ||
<DynamicContextProvider | ||
theme="auto" | ||
settings={{ | ||
environmentId: "2762a57b-faa4-41ce-9f16-abff9300e2c9", | ||
walletConnectors: [${walletConnectors}], | ||
}} | ||
> | ||
<Main /> | ||
</DynamicContextProvider> | ||
);`; | ||
} | ||
if (useWagmi) { | ||
imports += `import { DynamicWagmiConnector } from "@dynamic-labs/wagmi-connector";\n`; | ||
imports += `import { createConfig, WagmiProvider } from "wagmi";\n`; | ||
imports += `import { QueryClient, QueryClientProvider } from "@tanstack/react-query";\n`; | ||
imports += `import { http } from "viem";\n`; | ||
imports += `import { mainnet } from "viem/chains";\n`; | ||
} | ||
return `${imports}\n${content}\n\nexport default App;`; | ||
} | ||
imports += `import Main from "./Main";\n\n`; | ||
const walletConnectors = selectedChains | ||
.map((chain) => chain.connector) | ||
.join(", "); | ||
let content = ""; | ||
if (useWagmi) { | ||
content = `const config = createConfig({ | ||
chains: [mainnet], | ||
multiInjectedProviderDiscovery: false, | ||
transports: { | ||
[mainnet.id]: http(), | ||
}, | ||
}); | ||
const queryClient = new QueryClient(); | ||
const App = () => ( | ||
<DynamicContextProvider | ||
theme="auto" | ||
settings={{ | ||
environmentId: "2762a57b-faa4-41ce-9f16-abff9300e2c9", | ||
walletConnectors: [${walletConnectors}], | ||
}} | ||
> | ||
<WagmiProvider config={config}> | ||
<QueryClientProvider client={queryClient}> | ||
<DynamicWagmiConnector> | ||
<Main /> | ||
</DynamicWagmiConnector> | ||
</QueryClientProvider> | ||
</WagmiProvider> | ||
</DynamicContextProvider> | ||
);`; | ||
} else { | ||
content = `const App = () => ( | ||
<DynamicContextProvider | ||
theme="auto" | ||
settings={{ | ||
environmentId: "2762a57b-faa4-41ce-9f16-abff9300e2c9", | ||
walletConnectors: [${walletConnectors}], | ||
}} | ||
> | ||
<Main /> | ||
</DynamicContextProvider> | ||
);`; | ||
} | ||
return `${imports}\n${content}\n\nexport default App;`; | ||
} |
const main = ` | ||
import { DynamicWidget } from "@dynamic-labs/sdk-react-core"; | ||
import { useState, useEffect } from 'react'; | ||
import DynamicMethods from './Methods.js'; | ||
import './Main.css'; | ||
@@ -30,2 +31,3 @@ | ||
<DynamicWidget /> | ||
<DynamicMethods isDarkMode={isDarkMode} /> | ||
</div> | ||
@@ -41,4 +43,4 @@ <div className="footer"> | ||
export default Main; | ||
` | ||
`; | ||
export default main; |
@@ -7,6 +7,8 @@ const mainCss = ` | ||
align-items: center; | ||
justify-content: center; | ||
justify-content: flex-start; | ||
background: #f1f1f3; | ||
color: #333; | ||
transition: background-color 0.3s, color 0.3s; | ||
position: relative; | ||
padding-bottom: 60px; | ||
} | ||
@@ -74,17 +76,29 @@ | ||
justify-content: center; | ||
flex-grow: 1; | ||
width: 100%; | ||
padding: 60px 0; | ||
} | ||
.footer { | ||
position: absolute; | ||
bottom: 0; | ||
left: 0; | ||
right: 0; | ||
height: 60px; | ||
display: flex; | ||
align-items: center; | ||
justify-content: flex-end; | ||
padding-right: 20px; | ||
} | ||
.footer-image { | ||
position: absolute; | ||
bottom: 0px; | ||
right: 20px; | ||
height: 15rem; | ||
width: auto; | ||
margin-left: 8px; | ||
position: absolute; | ||
bottom: 0; | ||
right: 20px; | ||
} | ||
.footer-text { | ||
position: absolute; | ||
bottom: 6px; | ||
right: 20px; | ||
color: #666; | ||
@@ -96,6 +110,2 @@ font-size: 14px; | ||
.container.dark .footer { | ||
background-color: rgba(42, 42, 42, 0.8); | ||
} | ||
.container.dark .footer-text { | ||
@@ -120,4 +130,4 @@ color: #ccc; | ||
} | ||
` | ||
`; | ||
export default mainCss; |
export const addSolanaDependencies = (packageJson) => { | ||
packageJson.dependencies['crypto-browserify'] = "^3.12.0"; | ||
packageJson.dependencies['stream-browserify'] = "^3.0.0"; | ||
packageJson.dependencies['process'] = "^0.11.10"; | ||
packageJson.dependencies["crypto-browserify"] = "^3.12.0"; | ||
packageJson.dependencies["stream-browserify"] = "^3.0.0"; | ||
packageJson.dependencies["process"] = "^0.11.10"; | ||
// Add the browser field | ||
packageJson.browser = { | ||
...packageJson.browser, | ||
crypto: false | ||
crypto: false, | ||
}; | ||
@@ -10,0 +10,0 @@ // Add the solana overrides |
@@ -8,3 +8,4 @@ ## Dynamic Example App Generator | ||
- **Next.js Projects**: | ||
- `next-viem`: Next.js project using Viem. | ||
- `next-viem`: Next.js project using Viem. | ||
- `next-viem-wagmi`: Next.js project using both Viem and Wagmi. | ||
@@ -35,2 +36,1 @@ - `next-ethers`: Next.js project using ethers.js. | ||
The main logic for the generation can be found in the `generate-next.ts` and `generate-react.ts` files at the root of the project. | ||
118
utils.js
@@ -1,14 +0,42 @@ | ||
import inquirer from 'inquirer'; | ||
import chalk from 'chalk'; | ||
import fs from 'fs/promises'; | ||
import path from 'path'; | ||
import inquirer from "inquirer"; | ||
import chalk from "chalk"; | ||
import fs from "fs/promises"; | ||
import path from "path"; | ||
export const chainOptions = [ | ||
{ name: 'Ethereum', package: '@dynamic-labs/ethereum', connector: 'EthereumWalletConnectors' }, | ||
{ name: 'Algorand', package: '@dynamic-labs/algorand', connector: 'AlgorandWalletConnectors' }, | ||
{ name: 'Solana', package: '@dynamic-labs/solana', connector: 'SolanaWalletConnectors' }, | ||
{ name: 'Flow', package: '@dynamic-labs/flow', connector: 'FlowWalletConnectors' }, | ||
{ name: 'Starknet', package: '@dynamic-labs/starknet', connector: 'StarknetWalletConnectors' }, | ||
{ name: 'Cosmos', package: '@dynamic-labs/cosmos', connector: 'CosmosWalletConnectors' }, | ||
{ name: 'Bitcoin', package: '@dynamic-labs/bitcoin', connector: 'BitcoinWalletConnectors' }, | ||
{ | ||
name: "Ethereum", | ||
package: "@dynamic-labs/ethereum", | ||
connector: "EthereumWalletConnectors", | ||
}, | ||
{ | ||
name: "Algorand", | ||
package: "@dynamic-labs/algorand", | ||
connector: "AlgorandWalletConnectors", | ||
}, | ||
{ | ||
name: "Solana", | ||
package: "@dynamic-labs/solana", | ||
connector: "SolanaWalletConnectors", | ||
}, | ||
{ | ||
name: "Flow", | ||
package: "@dynamic-labs/flow", | ||
connector: "FlowWalletConnectors", | ||
}, | ||
{ | ||
name: "Starknet", | ||
package: "@dynamic-labs/starknet", | ||
connector: "StarknetWalletConnectors", | ||
}, | ||
{ | ||
name: "Cosmos", | ||
package: "@dynamic-labs/cosmos", | ||
connector: "CosmosWalletConnectors", | ||
}, | ||
{ | ||
name: "Bitcoin", | ||
package: "@dynamic-labs/bitcoin", | ||
connector: "BitcoinWalletConnectors", | ||
}, | ||
]; | ||
@@ -19,15 +47,38 @@ | ||
name: chain.name, | ||
value: index | ||
value: index, | ||
})); | ||
const answers = await inquirer.prompt([ | ||
{ | ||
type: 'checkbox', | ||
name: 'selectedChains', | ||
message: 'Select the chains you want to include:', | ||
choices: chainChoices | ||
while (true) { | ||
const answers = await inquirer.prompt([ | ||
{ | ||
type: "checkbox", | ||
name: "selectedChains", | ||
message: "Select the chains you want to include:", | ||
choices: chainChoices, | ||
}, | ||
]); | ||
if (answers.selectedChains.length === 0) { | ||
console.log(chalk.yellow("Warning: No chains selected.")); | ||
const confirmAnswer = await inquirer.prompt([ | ||
{ | ||
type: "list", | ||
name: "action", | ||
message: | ||
"Do you want to continue without selecting any chains or go back to the selection?", | ||
choices: [ | ||
{ name: "Continue without chains", value: "continue" }, | ||
{ name: "Go back to chain selection", value: "back" }, | ||
], | ||
}, | ||
]); | ||
if (confirmAnswer.action === "continue") { | ||
return []; | ||
} | ||
// If 'back' is selected, the loop will continue and prompt again | ||
} else { | ||
return answers.selectedChains.map((index) => chainOptions[index]); | ||
} | ||
]); | ||
return answers.selectedChains.map(index => chainOptions[index]); | ||
} | ||
}; | ||
@@ -48,21 +99,28 @@ | ||
if (existingDirs.length > 0) { | ||
console.log(chalk.yellow(`The following directories already exist: ${existingDirs.join(', ')}`)); | ||
console.log( | ||
chalk.yellow( | ||
`The following directories already exist: ${existingDirs.join(", ")}`, | ||
), | ||
); | ||
const answer = await inquirer.prompt({ | ||
type: 'confirm', | ||
name: 'overwrite', | ||
message: 'Do you want to overwrite these directories?', | ||
default: false | ||
type: "confirm", | ||
name: "overwrite", | ||
message: "Do you want to overwrite these directories?", | ||
default: false, | ||
}); | ||
if (!answer.overwrite) { | ||
console.log(chalk.red('Operation cancelled.')); | ||
console.log(chalk.red("Operation cancelled.")); | ||
process.exit(0); | ||
} else { | ||
for (const dir of existingDirs) { | ||
await fs.rm(path.join(parentDir, dir), { recursive: true, force: true }); | ||
await fs.rm(path.join(parentDir, dir), { | ||
recursive: true, | ||
force: true, | ||
}); | ||
} | ||
} | ||
} else { | ||
console.log(chalk.green('All directories are available for creation.')); | ||
console.log(chalk.green("All directories are available for creation.")); | ||
} | ||
}; | ||
}; |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 2 instances in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 2 instances in 1 package
429815
29
1862
5
1
4
+ Addednode-fetch@^3.3.2
+ Addeddata-uri-to-buffer@4.0.1(transitive)
+ Addedfetch-blob@3.2.0(transitive)
+ Addedformdata-polyfill@4.0.10(transitive)
+ Addednode-domexception@1.0.0(transitive)
+ Addednode-fetch@3.3.2(transitive)
+ Addedweb-streams-polyfill@3.3.3(transitive)