UNPKG

create-rainbow-app

Version:
659 lines (585 loc) 21.1 kB
#!/usr/bin/env node const { execSync } = require("child_process"); const fs = require("fs"); const path = require("path"); /** * Create Rainbow App - A production-ready Web3 dApp template generator * with Next.js, TypeScript, RainbowKit, Wagmi, viem & ShadCN/UI */ (async () => { try { // Dynamically import chalk const chalk = (await import("chalk")).default; // Display welcome banner displayWelcomeBanner(chalk); // Validate project name const projectName = validateProjectName(process.argv[2], chalk); const targetPath = path.join(process.cwd(), projectName); // Ask user for router preference const useAppRouter = await askForRouterPreference(chalk); // Determine package manager to use (bun or yarn) const packageManager = determinePackageManager(chalk); // Create Next.js app createNextApp(projectName, packageManager, useAppRouter, chalk); // Change directory to the target path process.chdir(targetPath); // Use the selected router type const isAppRouter = useAppRouter; // Set up Web3 configuration setupWeb3Config(chalk); // Install dependencies installDependencies(packageManager, chalk); // Configure UI components setupShadcnUI(packageManager, chalk); // Install additional wallet connector packages installWalletConnectors(packageManager, chalk); // Update starter template files updateTemplateFiles(isAppRouter, chalk); // Display success message displaySuccessMessage(projectName, packageManager, isAppRouter, chalk); } catch (error) { console.error(`\n❌ Error: ${error.message}`); process.exit(1); } })(); /** * Displays a colorful welcome banner * @param {Object} chalk - Chalk instance for colored output */ function displayWelcomeBanner(chalk) { console.clear(); console.log( chalk.cyan.bold(` ██╗ ██╗███████╗██████╗ ██████╗ ██████╗ █████╗ ██████╗ ██████╗ ██║ ██║██╔════╝██╔══██╗╚════██╗ ██╔══██╗██╔══██╗██╔══██╗██╔══██╗ ██║ █╗ ██║█████╗ ██████╔╝ █████╔╝ ██║ ██║███████║██████╔╝██████╔╝ ██║███╗██║██╔══╝ ██╔══██╗ ╚═══██╗ ██║ ██║██╔══██║██╔═══╝ ██╔═══╝ ╚███╔███╔╝███████╗██████╔╝██████╔╝ ██████╔╝██║ ██║██║ ██║ ╚══╝╚══╝ ╚══════╝╚═════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ `), ); console.log( chalk.blue.bold( " 🌈 Production-ready Web3 dApp template with Next.js, TypeScript, RainbowKit, Wagmi, viem & ShadCN/UI!", ), ); console.log( chalk.magenta.bold( " 📁 Supports both Pages Router and App Router architectures", ), ); console.log( chalk.gray( " ─────────────────────────────────────────────────────────────────────────────────────\n", ), ); } /** * Validates the project name from command line arguments * @param {string} name - Project name from command line * @param {Object} chalk - Chalk instance for colored output * @returns {string} Validated project name */ function validateProjectName(name, chalk) { if (!name) { throw new Error(chalk.red("Please provide a project name.")); } return name; } /** * Determines which package manager to use (bun or yarn) * @param {Object} chalk - Chalk instance for colored output * @returns {string} Package manager to use ('bun' or 'yarn') */ function determinePackageManager(chalk) { try { execSync("bun --version", { stdio: "ignore" }); return "bun"; } catch (bunError) { try { execSync("yarn --version", { stdio: "ignore" }); return "yarn"; } catch (yarnError) { console.error( chalk.red( "Neither Bun nor Yarn is installed. One of these is required to run this script.", ), ); console.log( chalk.yellow("Install Bun from:"), chalk.blue("https://bun.sh/"), ); console.log( chalk.yellow("Install Yarn from:"), chalk.blue("https://yarnpkg.com/getting-started/install"), ); process.exit(1); } } } /** * Asks user for router preference * @param {Object} chalk - Chalk instance for colored output * @returns {Promise<boolean>} True if App Router, false if Pages Router */ async function askForRouterPreference(chalk) { const readline = require("readline").createInterface({ input: process.stdin, output: process.stdout, }); return new Promise((resolve) => { console.log(chalk.blue("Which router would you like to use?")); console.log(chalk.yellow("1. Pages Router (Traditional)")); console.log(chalk.yellow("2. App Router (New, Recommended)")); readline.question( chalk.green("Enter your choice (1 or 2): "), (answer) => { readline.close(); const choice = answer.trim(); if (choice === "2") { console.log(chalk.blue("Selected: App Router")); resolve(true); } else { console.log(chalk.blue("Selected: Pages Router")); resolve(false); } }, ); }); } /** * Creates a new Next.js application with the specified configuration * @param {string} projectName - Name of the project * @param {string} packageManager - Package manager to use * @param {boolean} useAppRouter - Whether to use App Router * @param {Object} chalk - Chalk instance for colored output */ function createNextApp(projectName, packageManager, useAppRouter, chalk) { console.log( chalk.blue("Creating Next.js app with TypeScript and Tailwind CSS..."), ); const appFlag = useAppRouter ? "--app" : "--app=false"; const createNextCommand = packageManager === "bun" ? `bunx create-next-app@15 ${projectName} --typescript --tailwind --eslint --src-dir ${appFlag} --import-alias="@/*"` : `npx create-next-app@15 ${projectName} --typescript --tailwind --eslint --src-dir ${appFlag} --import-alias="@/*"`; try { execSync(createNextCommand, { stdio: "inherit" }); } catch (error) { throw new Error(`Failed to create Next.js app: ${error.message}`); } } /** * Sets up Web3 configuration files * @param {Object} chalk - Chalk instance for colored output */ function setupWeb3Config(chalk) { try { // Create wagmi.ts file in src folder console.log(chalk.blue("Creating wagmi.ts configuration...")); const wagmiContent = `import { getDefaultConfig } from "@rainbow-me/rainbowkit"; import { mainnet, polygon, optimism, arbitrum, base } from "viem/chains"; export const config = getDefaultConfig({ appName: "My RainbowKit App", projectId: "YOUR_PROJECT_ID", chains: [mainnet, polygon, optimism, arbitrum, base], ssr: true, // If your dApp uses server side rendering (SSR) });`; fs.writeFileSync(path.join("src", "wagmi.ts"), wagmiContent); // Create ABI folder and demo.json console.log(chalk.blue("Creating ABI demo.json...")); const abiDir = path.join("src", "ABI"); fs.mkdirSync(abiDir, { recursive: true }); const demoAbi = [ { inputs: [ { internalType: "string", name: "_greeting", type: "string", }, ], name: "setGreeting", outputs: [], stateMutability: "nonpayable", type: "function", }, { inputs: [], name: "greeting", outputs: [ { internalType: "string", name: "", type: "string", }, ], stateMutability: "view", type: "function", }, ]; fs.writeFileSync( path.join(abiDir, "demo.json"), JSON.stringify(demoAbi, null, 2), ); } catch (error) { throw new Error(`Failed to setup Web3 configuration: ${error.message}`); } } /** * Installs required dependencies * @param {string} packageManager - Package manager to use * @param {Object} chalk - Chalk instance for colored output */ function installDependencies(packageManager, chalk) { try { console.log(chalk.blue("Installing packages...")); const installCommand = packageManager === "bun" ? "bun add @rainbow-me/rainbowkit wagmi viem@2.x @tanstack/react-query" : "yarn add @rainbow-me/rainbowkit wagmi viem@2.x @tanstack/react-query"; execSync(installCommand, { stdio: "inherit" }); } catch (error) { throw new Error(`Failed to install dependencies: ${error.message}`); } } /** * Install additional wallet connector packages * @param {string} packageManager - Package manager to use (bun or yarn) * @param {Object} chalk - Chalk instance for colored output */ function installWalletConnectors(packageManager, chalk) { try { console.log(chalk.blue("Installing wallet connector packages...")); const packages = [ "@base-org/account", "@coinbase/wallet-sdk", "@metamask/sdk", "@safe-global/safe-apps-provider", "@safe-global/safe-apps-sdk", "@walletconnect/ethereum-provider", ].join(" "); const installCommand = packageManager === "bun" ? `bun add ${packages}` : `yarn add ${packages}`; execSync(installCommand, { stdio: "inherit" }); console.log( chalk.green("✓ Wallet connector packages installed successfully"), ); } catch (error) { throw new Error( `Failed to install wallet connector packages: ${error.message}`, ); } } /** * Setup and configure shadcn/ui * @param {string} packageManager - Package manager to use (bun or yarn) * @param {Object} chalk - Chalk instance for colored output */ function setupShadcnUI(packageManager, chalk) { try { console.log(chalk.blue("Initializing shadcn/ui...")); const shadcnCommand = packageManager === "bun" ? "bunx shadcn@latest init" : "npx shadcn@latest init"; execSync(shadcnCommand, { stdio: "inherit" }); const shadcnCommandInstall = packageManager === "bun" ? "bunx shadcn@latest add button" : "npx shadcn@latest add button"; execSync(shadcnCommandInstall, { stdio: "inherit" }); } catch (error) { throw new Error(`Failed to setup shadcn/ui: ${error.message}`); } } /** * Updates template files with custom content * @param {boolean} isAppRouter - Whether using App Router or Pages Router * @param {Object} chalk - Chalk instance for colored output */ function updateTemplateFiles(isAppRouter, chalk) { try { if (isAppRouter) { setupAppRouter(chalk); } else { setupPagesRouter(chalk); } } catch (error) { throw new Error(`Failed to update template files: ${error.message}`); } } /** * Sets up Pages Router template files * @param {Object} chalk - Chalk instance for colored output */ function setupPagesRouter(chalk) { // Update _app.tsx with providers console.log(chalk.blue("Setting up providers in _app.tsx...")); const appContent = `import '@/styles/globals.css'; import '@rainbow-me/rainbowkit/styles.css'; import type { AppProps } from 'next/app'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { WagmiProvider } from 'wagmi'; import { RainbowKitProvider } from '@rainbow-me/rainbowkit'; import { config } from '@/wagmi'; const queryClient = new QueryClient(); export default function App({ Component, pageProps }: AppProps) { return ( <WagmiProvider config={config}> <QueryClientProvider client={queryClient}> <RainbowKitProvider> <Component {...pageProps} /> </RainbowKitProvider> </QueryClientProvider> </WagmiProvider> ); }`; fs.writeFileSync(path.join("src", "pages", "_app.tsx"), appContent); // Update index.tsx with custom content console.log(chalk.blue("Updating index.tsx with custom content...")); const indexContent = `import { ConnectButton } from "@rainbow-me/rainbowkit"; import { Geist, Geist_Mono } from "next/font/google"; import Link from "next/link"; import { Button } from "@/components/ui/button"; const geistSans = Geist({ variable: "--font-geist-sans", subsets: ["latin"], }); const geistMono = Geist_Mono({ variable: "--font-geist-mono", subsets: ["latin"], }); export default function Home() { return ( <div className={\`\${geistSans.className} \${geistMono.className} grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]\`} > <main className="flex flex-col gap-10 row-start-2 items-center sm:items-start"> <div className="flex flex-col items-center gap-4"> <h1 className="text-4xl font-bold text-center"> 🎒 Web3 Dapp Template </h1> <p className="text-lg text-center text-gray-600 dark:text-gray-400"> Web3 dApp template with Next.js, TypeScript, Tailwind CSS & shadcn/ui </p> </div> <div className="flex flex-col items-center justify-center gap-6 w-full"> <ConnectButton /> <div className="w-full p-6 border border-gray-200 dark:border-gray-800 rounded-lg"> <h2 className="text-xl font-semibold mb-4 text-center"> 🚀 Get Started </h2> <ol className="list-inside list-decimal text-sm space-y-2 font-[family-name:var(--font-geist-mono)]"> <li> Edit{" "} <code className="bg-black/[.05] dark:bg-white/[.06] px-1 py-0.5 rounded font-semibold"> src/pages/index.tsx </code> </li> <li> Update your project ID in{" "} <code className="bg-black/[.05] dark:bg-white/[.06] px-1 py-0.5 rounded font-semibold"> src/wagmi.ts </code> </li> <li>Add shadcn/ui components as needed</li> <li>Build your Web3 application!</li> </ol> </div> </div> <div className="flex gap-4 items-center flex-col sm:flex-row"> <Link href="https://rainbowkit.com/docs" target="_blank" rel="noopener noreferrer" > <Button variant="secondary">📚 RainbowKit Docs</Button> </Link> <Link href="https://ui.shadcn.com" target="_blank" rel="noopener noreferrer" > <Button variant="outline">🎨 shadcn/ui</Button> </Link> </div> </main> <footer className="row-start-3 flex gap-[24px] flex-wrap items-center justify-center text-sm text-gray-500"> <span>Built with ❤️ using Web3 Dapp Template</span> </footer> </div> ); }`; fs.writeFileSync(path.join("src", "pages", "index.tsx"), indexContent); } /** * Sets up App Router template files * @param {Object} chalk - Chalk instance for colored output */ function setupAppRouter(chalk) { // Create Web3Provider directory console.log(chalk.blue("Creating Web3Provider directory...")); const providerDir = path.join("src", "Web3Provider"); fs.mkdirSync(providerDir, { recursive: true }); // Create Provider.tsx console.log(chalk.blue("Creating Provider.tsx...")); const providerContent = `"use client"; import { config } from "@/wagmi"; import { RainbowKitProvider } from "@rainbow-me/rainbowkit"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { WagmiProvider } from "wagmi"; const queryClient = new QueryClient(); export default function Provider({ children, }: Readonly<{ children: React.ReactNode; }>) { return ( <WagmiProvider config={config}> <QueryClientProvider client={queryClient}> <RainbowKitProvider>{children}</RainbowKitProvider> </QueryClientProvider> </WagmiProvider> ); }`; fs.writeFileSync(path.join(providerDir, "Provider.tsx"), providerContent); // Update layout.tsx console.log(chalk.blue("Updating layout.tsx...")); const layoutContent = `import type { Metadata } from "next"; import { Geist, Geist_Mono } from "next/font/google"; import "./globals.css"; import Provider from "@/Web3Provider/Provider"; import "@rainbow-me/rainbowkit/styles.css"; const geistSans = Geist({ variable: "--font-geist-sans", subsets: ["latin"], }); const geistMono = Geist_Mono({ variable: "--font-geist-mono", subsets: ["latin"], }); export const metadata: Metadata = { title: "Create Next App", description: "Generated by create next app", }; export default function RootLayout({ children, }: Readonly<{ children: React.ReactNode; }>) { return ( <html lang="en"> <body className={\`\${geistSans.variable} \${geistMono.variable} antialiased\`} > <Provider>{children}</Provider> </body> </html> ); }`; fs.writeFileSync(path.join("src", "app", "layout.tsx"), layoutContent); // Update page.tsx console.log(chalk.blue("Updating page.tsx...")); const pageContent = `import { ConnectButton } from "@rainbow-me/rainbowkit"; import { Geist, Geist_Mono } from "next/font/google"; import Link from "next/link"; import { Button } from "@/components/ui/button"; const geistSans = Geist({ variable: "--font-geist-sans", subsets: ["latin"], }); const geistMono = Geist_Mono({ variable: "--font-geist-mono", subsets: ["latin"], }); export default function Home() { return ( <div className={\`\${geistSans.className} \${geistMono.className} grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]\`} > <main className="flex flex-col gap-10 row-start-2 items-center sm:items-start"> <div className="flex flex-col items-center gap-4"> <h1 className="text-4xl font-bold text-center"> 🎒 Web3 Dapp Template </h1> <p className="text-lg text-center text-gray-600 dark:text-gray-400"> Web3 dApp template with Next.js, TypeScript, Tailwind CSS & shadcn/ui </p> </div> <div className="flex flex-col items-center justify-center gap-6 w-full"> <ConnectButton /> <div className="w-full p-6 border border-gray-200 dark:border-gray-800 rounded-lg"> <h2 className="text-xl font-semibold mb-4 text-center"> 🚀 Get Started </h2> <ol className="list-inside list-decimal text-sm space-y-2 font-[family-name:var(--font-geist-mono)]"> <li> Edit{" "} <code className="bg-black/[.05] dark:bg-white/[.06] px-1 py-0.5 rounded font-semibold"> src/app/page.tsx </code> </li> <li> Update your project ID in{" "} <code className="bg-black/[.05] dark:bg-white/[.06] px-1 py-0.5 rounded font-semibold"> src/wagmi.ts </code> </li> <li>Add shadcn/ui components as needed</li> <li>Build your Web3 application!</li> </ol> </div> </div> <div className="flex gap-4 items-center flex-col sm:flex-row"> <Link href="https://rainbowkit.com/docs" target="_blank" rel="noopener noreferrer" > <Button variant="secondary">📚 RainbowKit Docs</Button> </Link> <Link href="https://ui.shadcn.com" target="_blank" rel="noopener noreferrer" > <Button variant="outline">🎨 shadcn/ui</Button> </Link> </div> </main> <footer className="row-start-3 flex gap-[24px] flex-wrap items-center justify-center text-sm text-gray-500"> <span>Built with ❤️ using Web3 Dapp Template</span> </footer> </div> ); }`; fs.writeFileSync(path.join("src", "app", "page.tsx"), pageContent); } /** * Displays success message with next steps * @param {string} projectName - Name of the project * @param {string} packageManager - Package manager used * @param {boolean} isAppRouter - Whether using App Router or Pages Router * @param {Object} chalk - Chalk instance for colored output */ function displaySuccessMessage( projectName, packageManager, isAppRouter, chalk, ) { console.log(chalk.green(`\n✅ Project ${projectName} is ready!`)); console.log( chalk.yellow( `Router type: ${isAppRouter ? "App Router" : "Pages Router"}`, ), ); console.log(chalk.yellow(`To start working on your project, run:`)); console.log(chalk.cyan(`\t cd ${projectName}`)); console.log( chalk.cyan(`\t ${packageManager === "bun" ? "bun dev" : "yarn dev"}`), ); }