UNPKG

@webdevarif/create-next-app

Version:

A powerful CLI to scaffold Next.js projects with customizable features like authentication, internationalization, animations, and more.

1,539 lines (1,361 loc) 135 kB
#!/usr/bin/env node const { execSync } = require('child_process'); const fs = require('fs'); const path = require('path'); const chalk = require('chalk'); const prompts = require('prompts'); // Check for help flags if (process.argv.includes('--help') || process.argv.includes('-h')) { console.log(chalk.blue.bold('\n@webdevarif/create-next-app\n')); console.log(chalk.gray('A powerful CLI to scaffold Next.js projects with customizable features.\n')); console.log(chalk.yellow('Usage:')); console.log(' npx @webdevarif/create-next-app <project-name>'); console.log(' npx @webdevarif/create-next-app my-awesome-project\n'); console.log(chalk.yellow('Options:')); console.log(' --help, -h Show this help message'); console.log(' --version, -v Show version number\n'); console.log(chalk.yellow('Features:')); console.log(' • Next.js 14 with TypeScript'); console.log(' • Authentication (NextAuth.js)'); console.log(' • Database (Prisma + MySQL)'); console.log(' • Internationalization (next-intl)'); console.log(' • Animations (GSAP + Lenis)'); console.log(' • Data Fetching (SWR + Axios)'); console.log(' • State Management (Redux Toolkit)'); console.log(' • Loading Indicators (NProgress)'); console.log(' • UI Components (Tailwind + Shadcn)'); console.log(' • Theme Support (next-themes)'); console.log(' • Toast Notifications (Sonner)\n'); console.log(chalk.gray('For more information, visit: https://github.com/webdevarif/arif-create-next-app')); process.exit(0); } // Check for version flags if (process.argv.includes('--version') || process.argv.includes('-v')) { const packageJson = require('../package.json'); console.log(packageJson.version); process.exit(0); } async function main() { console.log(chalk.blue.bold('\n🚀 Welcome to arif-create-next-app!\n')); console.log(chalk.gray('I\'ll help you create a Next.js project with the features you need.\n')); // Get project name from command line arguments or prompt let projectName = process.argv[2]; if (!projectName) { const response = await prompts({ type: 'text', name: 'projectName', message: 'What is your project name?', validate: (value) => { if (!value) return 'Project name is required'; if (!/^[a-zA-Z0-9-_]+$/.test(value)) { return 'Project name can only contain letters, numbers, hyphens, and underscores'; } return true; } }); if (!response.projectName) { console.log(chalk.red('❌ Project name is required')); process.exit(1); } projectName = response.projectName; } // Validate project name if (!/^[a-zA-Z0-9-_]+$/.test(projectName)) { console.log(chalk.red('❌ Project name can only contain letters, numbers, hyphens, and underscores')); process.exit(1); } const projectPath = path.resolve(process.cwd(), projectName); // Check if directory already exists if (fs.existsSync(projectPath)) { console.log(chalk.red(`❌ Directory "${projectName}" already exists`)); process.exit(1); } // Check disk space before creating project try { const freeSpace = require('os').freemem(); const freeSpaceGB = freeSpace / (1024 * 1024 * 1024); if (freeSpaceGB < 1) { console.log(chalk.red('❌ Insufficient disk space!')); console.log(chalk.yellow(`💡 Available space: ${freeSpaceGB.toFixed(2)}GB`)); console.log(chalk.yellow('💡 You need at least 1-2GB of free space for a Next.js project.')); console.log(chalk.yellow('💡 Please free up some space and try again.')); process.exit(1); } } catch (error) { // If we can't check disk space, continue anyway console.log(chalk.yellow('⚠️ Could not check disk space, continuing...')); } // Feature selection console.log(chalk.blue('\n🔧 Let\'s configure your project features:\n')); const features = await prompts([ { type: 'confirm', name: 'authentication', message: 'Do you want authentication? (NextAuth.js + custom context/hooks)', initial: true }, { type: 'confirm', name: 'database', message: 'Do you want a database? (Prisma with MySQL)', initial: true }, { type: 'confirm', name: 'internationalization', message: 'Do you want internationalization? (next-intl)', initial: true }, { type: 'confirm', name: 'animations', message: 'Do you want animations? (GSAP + Lenis smooth scroll)', initial: true }, { type: 'select', name: 'dataFetching', message: 'Which data fetching library do you prefer?', choices: [ { title: 'SWR', value: 'swr' }, { title: 'Axios', value: 'axios' }, { title: 'Both SWR and Axios', value: 'both' }, { title: 'None', value: 'none' } ], initial: 0 }, { type: 'confirm', name: 'stateManagement', message: 'Do you want state management? (Redux Toolkit)', initial: true }, { type: 'confirm', name: 'loadingIndicators', message: 'Do you want loading indicators? (nprogress)', initial: true }, { type: 'confirm', name: 'tailwind', message: 'Do you want Tailwind CSS?', initial: true }, { type: (prev) => prev ? 'confirm' : null, name: 'shadcn', message: 'Do you want Shadcn UI components? (requires Tailwind CSS)', initial: true } ]); // Note: Authentication can work without database (using mock data) if (features.authentication && !features.database) { console.log(chalk.blue('ℹ️ Note: Authentication will use mock data without database.')); } try { console.log(chalk.yellow(`\n📁 Creating project directory...`)); fs.mkdirSync(projectPath, { recursive: true }); console.log(chalk.yellow(`📥 Generating project files...`)); // Generate project based on selected features await generateProject(projectPath, projectName, features); console.log(chalk.yellow(`📦 Installing dependencies...`)); // Change to project directory and install dependencies process.chdir(projectPath); // Install dependencies with better error handling try { // Add a small delay to help with Windows file system locks await new Promise(resolve => setTimeout(resolve, 1000)); execSync('npm install', { stdio: 'inherit' }); } catch (error) { console.error(chalk.red(' ❌ Error installing dependencies:'), error.message); // Check for specific error types if (error.message.includes('ENOSPC') || error.message.includes('no space left on device')) { console.log(chalk.red(' 💾 Disk space issue detected!')); console.log(chalk.yellow(' 💡 Your disk is full. Please free up some space and try again.')); console.log(chalk.yellow(' 💡 You need at least 1-2GB of free space for a Next.js project.')); console.log(chalk.blue(' ℹ️ Project structure created, but dependencies could not be installed.')); } else if (error.message.includes('EPERM') || error.message.includes('EBUSY')) { console.log(chalk.yellow(' 💡 This might be due to file system locks on Windows.')); console.log(chalk.yellow(' 💡 Try running the CLI again or manually run "npm install" in the project directory.')); console.log(chalk.blue(' ℹ️ Project created successfully, but you may need to run "npm install" manually.')); } else { console.log(chalk.yellow(' 💡 Try running the CLI again or manually run "npm install" in the project directory.')); console.log(chalk.blue(' ℹ️ Project created successfully, but you may need to run "npm install" manually.')); } } console.log(chalk.green(`\n✅ Project "${projectName}" created successfully!`)); console.log(chalk.blue('\n📋 Next steps:')); console.log(chalk.gray(` cd ${projectName}`)); if (features.database) { console.log(chalk.gray(' cp .env.example .env.local')); console.log(chalk.gray(' # Update .env.local with your database URL')); console.log(chalk.gray(' npx prisma generate')); console.log(chalk.gray(' npx prisma db push')); } console.log(chalk.gray(' npm run dev')); console.log(chalk.blue('\n🎉 Happy coding!')); } catch (error) { console.log(chalk.red(`❌ Error creating project: ${error.message}`)); // Clean up on error with better Windows handling if (fs.existsSync(projectPath)) { try { fs.rmSync(projectPath, { recursive: true, force: true }); } catch (cleanupError) { console.error(chalk.red('❌ Error cleaning up project directory:'), cleanupError.message); console.log(chalk.yellow('💡 This might be due to file system locks on Windows.')); console.log(chalk.yellow('💡 You may need to manually delete the project directory.')); console.log(chalk.gray(` Directory: ${projectPath}`)); } } process.exit(1); } } // Generate project based on selected features async function generateProject(projectPath, projectName, features) { // Create basic Next.js structure await createBasicStructure(projectPath, projectName, features); // Add features based on selections if (features.authentication) { await addAuthentication(projectPath, features); } if (features.database) { await addDatabase(projectPath, features); } if (features.internationalization) { await addInternationalization(projectPath, features); } if (features.animations) { await addAnimations(projectPath, features); } if (features.dataFetching !== 'none') { await addDataFetching(projectPath, features); } if (features.stateManagement) { await addStateManagement(projectPath, features); } if (features.loadingIndicators) { await addLoadingIndicators(projectPath, features); } if (features.tailwind) { await addUIComponents(projectPath, features); } } // Create basic Next.js structure async function createBasicStructure(projectPath, projectName, features) { // Create directories const dirs = [ 'src/app', 'src/components', 'public' ]; // Only create src/lib when internationalization is NOT selected if (!features.internationalization) { dirs.push('src/lib'); } for (const dir of dirs) { fs.mkdirSync(path.join(projectPath, dir), { recursive: true }); } // Create package.json const packageJson = { name: projectName, version: "0.1.0", private: true, scripts: { dev: "next dev", build: features.database ? "prisma generate && next build" : "next build", start: "next start", lint: "next lint" }, dependencies: { "next": "^14.2.30", "react": "^18", "react-dom": "^18" }, devDependencies: { "@types/node": "^20.14.13", "@types/react": "^18", "@types/react-dom": "^18", "eslint": "^8", "eslint-config-next": "14.2.5", "typescript": "^5.5.4" } }; // Add dependencies based on features if (features.database) { packageJson.dependencies["@prisma/client"] = "^6.11.1"; packageJson.devDependencies["prisma"] = "^6.11.1"; packageJson.prisma = { schema: "prisma/schema.prisma" }; } if (features.authentication) { packageJson.dependencies["next-auth"] = "^5.0.0-beta.19"; if (features.database) { packageJson.dependencies["@auth/prisma-adapter"] = "^2.10.0"; } packageJson.dependencies["bcryptjs"] = "^2.4.3"; packageJson.devDependencies["@types/bcryptjs"] = "^2.4.6"; } if (features.internationalization) { packageJson.dependencies["next-intl"] = "^3.17.2"; } if (features.animations) { packageJson.dependencies["gsap"] = "^3.13.0"; packageJson.dependencies["@gsap/react"] = "^2.1.2"; packageJson.dependencies["lenis"] = "^1.3.4"; } if (features.dataFetching === 'swr' || features.dataFetching === 'both') { packageJson.dependencies["swr"] = "^2.3.3"; } if (features.dataFetching === 'axios' || features.dataFetching === 'both') { packageJson.dependencies["axios"] = "^1.7.7"; } if (features.stateManagement) { packageJson.dependencies["@reduxjs/toolkit"] = "^2.8.2"; packageJson.dependencies["react-redux"] = "^9.2.0"; } if (features.loadingIndicators) { packageJson.dependencies["nprogress"] = "^0.2.0"; packageJson.devDependencies["@types/nprogress"] = "^0.2.3"; } if (features.tailwind) { packageJson.devDependencies["autoprefixer"] = "^10.4.20"; packageJson.devDependencies["postcss"] = "^8"; packageJson.devDependencies["tailwindcss"] = "^3.4.1"; if (features.shadcn) { packageJson.dependencies["@radix-ui/react-slot"] = "^1.2.3"; packageJson.dependencies["class-variance-authority"] = "^0.7.1"; packageJson.dependencies["clsx"] = "^2.1.1"; packageJson.dependencies["tailwind-merge"] = "^2.4.0"; packageJson.dependencies["tailwindcss-animate"] = "^1.0.7"; packageJson.dependencies["lucide-react"] = "^0.414.0"; packageJson.dependencies["next-themes"] = "^0.4.6"; packageJson.dependencies["sonner"] = "^2.0.5"; } } fs.writeFileSync( path.join(projectPath, 'package.json'), JSON.stringify(packageJson, null, 2) ); // Create next.config.mjs let nextConfig = `/** @type {import('next').NextConfig} */ const nextConfig = { experimental: { serverComponentsExternalPackages: [], }, `; if (features.internationalization) { nextConfig = `import createNextIntlPlugin from 'next-intl/plugin'; const withNextIntl = createNextIntlPlugin('./i18n/request.ts'); /** @type {import('next').NextConfig} */ const nextConfig = { experimental: { serverComponentsExternalPackages: [], }, `; nextConfig += ` env: { // DATABASE DATABASE_URL: process.env.DATABASE_URL, // NEXTAUTH NEXTAUTH_SECRET: process.env.NEXTAUTH_SECRET, NEXTAUTH_URL: process.env.NEXTAUTH_URL, // AUTH_GOOGLE AUTH_GOOGLE_ID: process.env.AUTH_GOOGLE_ID, AUTH_GOOGLE_SECRET: process.env.AUTH_GOOGLE_SECRET, }, images: { remotePatterns: [ { protocol: 'https', hostname: '**', }, ], }, async headers() { return [ { // matching all API routes source: "/api/:path*", headers: [ { key: "Access-Control-Allow-Credentials", value: "true" }, { key: "Access-Control-Allow-Origin", value: "*" }, { key: "Access-Control-Allow-Methods", value: "GET,DELETE,PATCH,POST,PUT,OPTIONS" }, { key: "Access-Control-Allow-Headers", value: "X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version, Authorization, X-API-Key" }, ] } ] }, }; export default ${features.internationalization ? 'withNextIntl(nextConfig)' : 'nextConfig'};`; } else { nextConfig += `}; export default nextConfig;`; } fs.writeFileSync(path.join(projectPath, 'next.config.mjs'), nextConfig); // Create basic app structure let appLayout; if (features.internationalization) { appLayout = `import type { Metadata } from 'next' export const metadata: Metadata = { title: '${projectName}', description: 'Generated by arif-create-next-app', } export default function RootLayout({ children, }: { children: React.ReactNode }) { return ( <html lang="en"> <body>{children}</body> </html> ) }`; } else { appLayout = `import type { Metadata } from 'next' import './globals.css' export const metadata: Metadata = { title: '${projectName}', description: 'Generated by arif-create-next-app', } export default function RootLayout({ children, }: { children: React.ReactNode }) { return ( <html lang="en"> <body>{children}</body> </html> ) }`; } fs.writeFileSync(path.join(projectPath, 'src/app/layout.tsx'), appLayout); // Create basic page with conditional styling let pageContent; if (features.animations && features.tailwind && features.shadcn) { pageContent = `'use client'; import { FadeIn, Scale, Stagger, ScrollReveal } from '@/components'; export default function Home() { return ( <div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100"> {/* Hero Section */} <section className="flex flex-col items-center justify-center min-h-screen py-20 px-4"> <FadeIn direction="up" delay={0.2}> <h1 className="text-6xl font-bold text-gray-900 mb-6"> Welcome to ${projectName}! </h1> </FadeIn> <FadeIn direction="up" delay={0.4}> <p className="text-xl text-gray-600 mb-8 max-w-2xl text-center"> A modern Next.js application with beautiful animations and smooth interactions. </p> </FadeIn> <Scale delay={0.6}> <div className="bg-white rounded-lg shadow-lg p-6 mb-8"> <code className="text-lg font-mono text-gray-800"> Get started by editing src/app/page.tsx </code> </div> </Scale> </section> {/* Features Section */} <section className="py-20 px-4"> <div className="max-w-6xl mx-auto"> <ScrollReveal direction="up"> <h2 className="text-4xl font-bold text-center text-gray-900 mb-16"> Animation Components </h2> </ScrollReveal> <Stagger stagger={0.2}> <div className="grid md:grid-cols-2 lg:grid-cols-4 gap-8"> <ScrollReveal direction="up"> <div className="bg-white rounded-lg shadow-lg p-6 text-center"> <div className="w-16 h-16 bg-blue-500 rounded-full mx-auto mb-4 flex items-center justify-center"> <span className="text-white text-2xl">✨</span> </div> <h3 className="text-xl font-semibold mb-2">Fade In</h3> <p className="text-gray-600">Smooth fade in animations with directional support</p> </div> </ScrollReveal> <ScrollReveal direction="up"> <div className="bg-white rounded-lg shadow-lg p-6 text-center"> <div className="w-16 h-16 bg-green-500 rounded-full mx-auto mb-4 flex items-center justify-center"> <span className="text-white text-2xl">📏</span> </div> <h3 className="text-xl font-semibold mb-2">Scale</h3> <p className="text-gray-600">Scale animations with bounce effects</p> </div> </ScrollReveal> <ScrollReveal direction="up"> <div className="bg-white rounded-lg shadow-lg p-6 text-center"> <div className="w-16 h-16 bg-purple-500 rounded-full mx-auto mb-4 flex items-center justify-center"> <span className="text-white text-2xl">🎭</span> </div> <h3 className="text-xl font-semibold mb-2">Stagger</h3> <p className="text-gray-600">Staggered animations for multiple elements</p> </div> </ScrollReveal> <ScrollReveal direction="up"> <div className="bg-white rounded-lg shadow-lg p-6 text-center"> <div className="w-16 h-16 bg-orange-500 rounded-full mx-auto mb-4 flex items-center justify-center"> <span className="text-white text-2xl">👁️</span> </div> <h3 className="text-xl font-semibold mb-2">Scroll Reveal</h3> <p className="text-gray-600">Animations triggered by scroll position</p> </div> </ScrollReveal> </div> </Stagger> </div> </section> {/* CTA Section */} <section className="py-20 px-4 bg-gray-900 text-white"> <div className="max-w-4xl mx-auto text-center"> <FadeIn direction="up"> <h2 className="text-4xl font-bold mb-6"> Ready to build something amazing? </h2> </FadeIn> <FadeIn direction="up" delay={0.2}> <p className="text-xl mb-8 text-gray-300"> Start building your next project with these powerful animation components. </p> </FadeIn> <Scale delay={0.4}> <button className="bg-blue-600 hover:bg-blue-700 text-white font-bold py-3 px-8 rounded-lg text-lg transition-colors"> Get Started </button> </Scale> </div> </section> </div> ); }`; } else if (features.animations && features.tailwind && !features.shadcn) { pageContent = `'use client'; import { FadeIn, Scale, Stagger, ScrollReveal } from '@/components'; export default function Home() { return ( <div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100"> {/* Hero Section */} <section className="flex flex-col items-center justify-center min-h-screen py-20 px-4"> <FadeIn direction="up" delay={0.2}> <h1 className="text-6xl font-bold text-gray-900 mb-6"> Welcome to ${projectName}! </h1> </FadeIn> <FadeIn direction="up" delay={0.4}> <p className="text-xl text-gray-600 mb-8 max-w-2xl text-center"> A modern Next.js application with beautiful animations and smooth interactions. </p> </FadeIn> <Scale delay={0.6}> <div className="bg-white rounded-lg shadow-lg p-6 mb-8"> <code className="text-lg font-mono text-gray-800"> Get started by editing src/app/page.tsx </code> </div> </Scale> </section> {/* Features Section */} <section className="py-20 px-4"> <div className="max-w-6xl mx-auto"> <ScrollReveal direction="up"> <h2 className="text-4xl font-bold text-center text-gray-900 mb-16"> Animation Components </h2> </ScrollReveal> <Stagger stagger={0.2}> <div className="grid md:grid-cols-2 lg:grid-cols-4 gap-8"> <ScrollReveal direction="up"> <div className="bg-white rounded-lg shadow-lg p-6 text-center"> <div className="w-16 h-16 bg-blue-500 rounded-full mx-auto mb-4 flex items-center justify-center"> <span className="text-white text-2xl">✨</span> </div> <h3 className="text-xl font-semibold mb-2">Fade In</h3> <p className="text-gray-600">Smooth fade in animations with directional support</p> </div> </ScrollReveal> <ScrollReveal direction="up"> <div className="bg-white rounded-lg shadow-lg p-6 text-center"> <div className="w-16 h-16 bg-green-500 rounded-full mx-auto mb-4 flex items-center justify-center"> <span className="text-white text-2xl">📏</span> </div> <h3 className="text-xl font-semibold mb-2">Scale</h3> <p className="text-gray-600">Scale animations with bounce effects</p> </div> </ScrollReveal> <ScrollReveal direction="up"> <div className="bg-white rounded-lg shadow-lg p-6 text-center"> <div className="w-16 h-16 bg-purple-500 rounded-full mx-auto mb-4 flex items-center justify-center"> <span className="text-white text-2xl">🎭</span> </div> <h3 className="text-xl font-semibold mb-2">Stagger</h3> <p className="text-gray-600">Staggered animations for multiple elements</p> </div> </ScrollReveal> <ScrollReveal direction="up"> <div className="bg-white rounded-lg shadow-lg p-6 text-center"> <div className="w-16 h-16 bg-orange-500 rounded-full mx-auto mb-4 flex items-center justify-center"> <span className="text-white text-2xl">👁️</span> </div> <h3 className="text-xl font-semibold mb-2">Scroll Reveal</h3> <p className="text-gray-600">Animations triggered by scroll position</p> </div> </ScrollReveal> </div> </Stagger> </div> </section> {/* CTA Section */} <section className="py-20 px-4 bg-gray-900 text-white"> <div className="max-w-4xl mx-auto text-center"> <FadeIn direction="up"> <h2 className="text-4xl font-bold mb-6"> Ready to build something amazing? </h2> </FadeIn> <FadeIn direction="up" delay={0.2}> <p className="text-xl mb-8 text-gray-300"> Start building your next project with these powerful animation components. </p> </FadeIn> <Scale delay={0.4}> <button className="bg-blue-600 hover:bg-blue-700 text-white font-bold py-3 px-8 rounded-lg text-lg transition-colors"> Get Started </button> </Scale> </div> </section> </div> ); }`; } else if (features.animations && !features.tailwind) { pageContent = `'use client'; import { FadeIn, Scale, Stagger, ScrollReveal } from '@/components'; export default function Home() { return ( <div style={{ minHeight: '100vh', background: 'linear-gradient(135deg, #f0f9ff 0%, #e0e7ff 100%)' }}> {/* Hero Section */} <section style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', minHeight: '100vh', padding: '80px 16px' }}> <FadeIn direction="up" delay={0.2}> <h1 style={{ fontSize: '3.75rem', fontWeight: 'bold', color: '#111827', marginBottom: '24px', textAlign: 'center' }}> Welcome to ${projectName}! </h1> </FadeIn> <FadeIn direction="up" delay={0.4}> <p style={{ fontSize: '1.25rem', color: '#6b7280', marginBottom: '32px', maxWidth: '32rem', textAlign: 'center' }}> A modern Next.js application with beautiful animations and smooth interactions. </p> </FadeIn> <Scale delay={0.6}> <div style={{ backgroundColor: 'white', borderRadius: '8px', boxShadow: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)', padding: '24px', marginBottom: '32px' }}> <code style={{ fontSize: '1.125rem', fontFamily: 'monospace', color: '#1f2937' }}> Get started by editing src/app/page.tsx </code> </div> </Scale> </section> {/* Features Section */} <section style={{ padding: '80px 16px', backgroundColor: '#f9fafb' }}> <div style={{ maxWidth: '72rem', margin: '0 auto' }}> <ScrollReveal direction="up"> <h2 style={{ fontSize: '2.25rem', fontWeight: 'bold', textAlign: 'center', color: '#111827', marginBottom: '64px' }}> Animation Components </h2> </ScrollReveal> <Stagger stagger={0.2}> <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(250px, 1fr))', gap: '32px' }}> <ScrollReveal direction="up"> <div style={{ backgroundColor: 'white', borderRadius: '8px', boxShadow: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)', padding: '24px', textAlign: 'center' }}> <div style={{ width: '64px', height: '64px', backgroundColor: '#3b82f6', borderRadius: '50%', margin: '0 auto 16px', display: 'flex', alignItems: 'center', justifyContent: 'center' }}> <span style={{ color: 'white', fontSize: '1.5rem' }}>✨</span> </div> <h3 style={{ fontSize: '1.25rem', fontWeight: '600', marginBottom: '8px' }}>Fade In</h3> <p style={{ color: '#6b7280' }}>Smooth fade in animations with directional support</p> </div> </ScrollReveal> <ScrollReveal direction="up"> <div style={{ backgroundColor: 'white', borderRadius: '8px', boxShadow: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)', padding: '24px', textAlign: 'center' }}> <div style={{ width: '64px', height: '64px', backgroundColor: '#10b981', borderRadius: '50%', margin: '0 auto 16px', display: 'flex', alignItems: 'center', justifyContent: 'center' }}> <span style={{ color: 'white', fontSize: '1.5rem' }}>📏</span> </div> <h3 style={{ fontSize: '1.25rem', fontWeight: '600', marginBottom: '8px' }}>Scale</h3> <p style={{ color: '#6b7280' }}>Scale animations with bounce effects</p> </div> </ScrollReveal> <ScrollReveal direction="up"> <div style={{ backgroundColor: 'white', borderRadius: '8px', boxShadow: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)', padding: '24px', textAlign: 'center' }}> <div style={{ width: '64px', height: '64px', backgroundColor: '#8b5cf6', borderRadius: '50%', margin: '0 auto 16px', display: 'flex', alignItems: 'center', justifyContent: 'center' }}> <span style={{ color: 'white', fontSize: '1.5rem' }}>🎭</span> </div> <h3 style={{ fontSize: '1.25rem', fontWeight: '600', marginBottom: '8px' }}>Stagger</h3> <p style={{ color: '#6b7280' }}>Staggered animations for multiple elements</p> </div> </ScrollReveal> <ScrollReveal direction="up"> <div style={{ backgroundColor: 'white', borderRadius: '8px', boxShadow: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)', padding: '24px', textAlign: 'center' }}> <div style={{ width: '64px', height: '64px', backgroundColor: '#f59e0b', borderRadius: '50%', margin: '0 auto 16px', display: 'flex', alignItems: 'center', justifyContent: 'center' }}> <span style={{ color: 'white', fontSize: '1.5rem' }}>👁️</span> </div> <h3 style={{ fontSize: '1.25rem', fontWeight: '600', marginBottom: '8px' }}>Scroll Reveal</h3> <p style={{ color: '#6b7280' }}>Animations triggered by scroll position</p> </div> </ScrollReveal> </div> </Stagger> </div> </section> {/* CTA Section */} <section style={{ padding: '80px 16px', backgroundColor: '#111827', color: 'white' }}> <div style={{ maxWidth: '56rem', margin: '0 auto', textAlign: 'center' }}> <FadeIn direction="up"> <h2 style={{ fontSize: '2.25rem', fontWeight: 'bold', marginBottom: '24px' }}> Ready to build something amazing? </h2> </FadeIn> <FadeIn direction="up" delay={0.2}> <p style={{ fontSize: '1.25rem', marginBottom: '32px', color: '#d1d5db' }}> Start building your next project with these powerful animation components. </p> </FadeIn> <Scale delay={0.4}> <button style={{ backgroundColor: '#2563eb', color: 'white', fontWeight: 'bold', padding: '12px 32px', borderRadius: '8px', fontSize: '1.125rem', border: 'none', cursor: 'pointer', transition: 'background-color 0.15s ease-in-out' }} onMouseEnter={(e) => { (e.target as HTMLButtonElement).style.backgroundColor = '#1d4ed8'; }} onMouseLeave={(e) => { (e.target as HTMLButtonElement).style.backgroundColor = '#2563eb'; }}> Get Started </button> </Scale> </div> </section> </div> ); }`; } else if (features.tailwind && features.shadcn) { pageContent = `export default function Home() { return ( <div className="flex flex-col items-center justify-center min-h-screen py-2"> <main className="flex flex-col items-center justify-center flex-1 px-20 text-center"> <h1 className="text-6xl font-bold"> Welcome to ${projectName}! </h1> <p className="mt-3 text-2xl"> Get started by editing{' '} <code className="p-3 font-mono text-lg bg-gray-100 rounded-md"> src/app/page.tsx </code> </p> </main> </div> ); }`; } else if (features.tailwind && !features.shadcn) { pageContent = `export default function Home() { return ( <div className="flex flex-col items-center justify-center min-h-screen py-2"> <main className="flex flex-col items-center justify-center flex-1 px-20 text-center"> <h1 className="text-6xl font-bold"> Welcome to ${projectName}! </h1> <p className="mt-3 text-2xl"> Get started by editing{' '} <code className="p-3 font-mono text-lg bg-gray-100 rounded-md"> src/app/page.tsx </code> </p> </main> </div> ); }`; } else { pageContent = `export default function Home() { return ( <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', minHeight: '100vh', padding: '8px 0' }}> <main style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', flex: 1, padding: '0 80px', textAlign: 'center' }}> <h1 style={{ fontSize: '3.75rem', fontWeight: 'bold', margin: 0 }}> Welcome to ${projectName}! </h1> <p style={{ marginTop: '12px', fontSize: '1.5rem', margin: '12px 0 0 0' }}> Get started by editing{' '} <code style={{ padding: '12px', fontFamily: 'monospace', fontSize: '1.125rem', backgroundColor: '#f3f4f6', borderRadius: '6px' }}> src/app/page.tsx </code> </p> </main> </div> ); }`; } // Only create pages in src/app when internationalization is NOT selected if (!features.internationalization) { fs.writeFileSync(path.join(projectPath, 'src/app/page.tsx'), pageContent); } // Create basic globals.css with conditional styling let globalsCss; if (features.tailwind) { if (features.shadcn) { globalsCss = `@tailwind base; @tailwind components; @tailwind utilities; @layer base { :root { --background: 0 0% 100%; --foreground: 222.2 84% 4.9%; --card: 0 0% 100%; --card-foreground: 222.2 84% 4.9%; --popover: 0 0% 100%; --popover-foreground: 222.2 84% 4.9%; --primary: 221.2 83.2% 53.3%; --primary-foreground: 210 40% 98%; --secondary: 210 40% 96%; --secondary-foreground: 222.2 84% 4.9%; --muted: 210 40% 96%; --muted-foreground: 215.4 16.3% 46.9%; --accent: 210 40% 96%; --accent-foreground: 222.2 84% 4.9%; --destructive: 0 84.2% 60.2%; --destructive-foreground: 210 40% 98%; --border: 214.3 31.8% 91.4%; --input: 214.3 31.8% 91.4%; --ring: 221.2 83.2% 53.3%; --radius: 0.5rem; } .dark { --background: 222.2 84% 4.9%; --foreground: 210 40% 98%; --card: 222.2 84% 4.9%; --card-foreground: 210 40% 98%; --popover: 222.2 84% 4.9%; --popover-foreground: 210 40% 98%; --primary: 217.2 91.2% 59.8%; --primary-foreground: 222.2 84% 4.9%; --secondary: 217.2 32.6% 17.5%; --secondary-foreground: 210 40% 98%; --muted: 217.2 32.6% 17.5%; --muted-foreground: 215 20.2% 65.1%; --accent: 217.2 32.6% 17.5%; --accent-foreground: 210 40% 98%; --destructive: 0 62.8% 30.6%; --destructive-foreground: 210 40% 98%; --border: 217.2 32.6% 17.5%; --input: 217.2 32.6% 17.5%; --ring: 224.3 76.3% 94.1%; } } @layer base { * { @apply border-border; } body { @apply bg-background text-foreground; } }`; } else { globalsCss = `@tailwind base; @tailwind components; @tailwind utilities; :root { --foreground-rgb: 0, 0, 0; --background-start-rgb: 214, 219, 220; --background-end-rgb: 255, 255, 255; } @media (prefers-color-scheme: dark) { :root { --foreground-rgb: 255, 255, 255; --background-start-rgb: 0, 0, 0; --background-end-rgb: 0, 0, 0; } } body { color: rgb(var(--foreground-rgb)); background: linear-gradient( to bottom, transparent, rgb(var(--background-end-rgb)) ) rgb(var(--background-start-rgb)); }`; } } else { globalsCss = `/* Global Styles */ * { box-sizing: border-box; padding: 0; margin: 0; } html, body { max-width: 100vw; overflow-x: hidden; } body { color: #000; background: #fff; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } a { color: inherit; text-decoration: none; } @media (prefers-color-scheme: dark) { html { color-scheme: dark; } body { color: #fff; background: #000; } }`; } // Only create globals.css in src/app when internationalization is NOT selected if (!features.internationalization) { fs.writeFileSync(path.join(projectPath, 'src/app/globals.css'), globalsCss); } // Create .eslintrc.json const eslintConfig = { extends: "next/core-web-vitals" }; fs.writeFileSync( path.join(projectPath, '.eslintrc.json'), JSON.stringify(eslintConfig, null, 2) ); // Create tsconfig.json const tsconfig = { compilerOptions: { target: "es5", lib: ["dom", "dom.iterable", "es6"], allowJs: true, skipLibCheck: true, strict: true, noEmit: true, esModuleInterop: true, module: "esnext", moduleResolution: "bundler", resolveJsonModule: true, isolatedModules: true, jsx: "preserve", incremental: true, plugins: [ { name: "next" } ], baseUrl: ".", paths: { "@/*": ["./src/*"] } }, include: ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], exclude: ["node_modules"] }; fs.writeFileSync( path.join(projectPath, 'tsconfig.json'), JSON.stringify(tsconfig, null, 2) ); // Create .gitignore const gitignore = `# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # dependencies /node_modules /.pnp .pnp.js # testing /coverage # next.js /.next/ /out/ # production /build # misc .DS_Store *.pem # debug npm-debug.log* yarn-debug.log* yarn-error.log* # local env files .env*.local # vercel .vercel # typescript *.tsbuildinfo next-env.d.ts # prisma /prisma/migrations `; fs.writeFileSync(path.join(projectPath, '.gitignore'), gitignore); // Create README.md const readme = `# ${projectName} This is a [Next.js](https://nextjs.org/) project bootstrapped with [arif-create-next-app](https://github.com/webdevarif/arif-create-next-app). ## Getting Started First, run the development server: \`\`\`bash npm run dev # or yarn dev # or pnpm dev # or bun dev \`\`\` Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. You can start editing the page by modifying \`src/app/page.tsx\`. The page auto-updates as you edit the file. ## Features ${features.authentication ? '✅ Authentication (NextAuth.js)' : '❌ Authentication'} ${features.database ? '✅ Database (Prisma)' : '❌ Database'} ${features.internationalization ? '✅ Internationalization (next-intl)' : '❌ Internationalization'} ${features.animations ? '✅ Animations (GSAP + Lenis)' : '❌ Animations'} ${features.dataFetching !== 'none' ? `✅ Data Fetching (${features.dataFetching})` : '❌ Data Fetching'} ${features.stateManagement ? '✅ State Management (Redux)' : '❌ State Management'} ${features.loadingIndicators ? '✅ Loading Indicators (nprogress)' : '❌ Loading Indicators'} ${features.tailwind ? (features.shadcn ? '✅ Tailwind CSS + Shadcn UI' : '✅ Tailwind CSS only') : '❌ UI Components'} ## Learn More To learn more about Next.js, take a look at the following resources: - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! ## Deploy on Vercel The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. `; fs.writeFileSync(path.join(projectPath, 'README.md'), readme); // Create middleware.ts if authentication or internationalization is selected if (features.authentication || features.internationalization) { const middlewareTs = `import { NextRequest, NextResponse } from 'next/server'; ${features.internationalization ? `import createMiddleware from 'next-intl/middleware'; import { locales } from '${features.internationalization ? '@/app/[locale]/lib/navigation' : '@/lib/navigation'}';` : ''} ${features.authentication ? `import { auth } from "${features.internationalization ? '@/app/[locale]/lib/auth' : '@/lib/auth'}";` : ''} import { removeLocalePrefix } from '${features.internationalization ? '@/app/[locale]/lib/utils' : '@/lib/utils'}'; ${features.authentication ? `import { PUBLIC_ROUTES, LOGIN, API_AUTH_PREFIX, AUTH_ROUTES, PROTECTED_ROUTES, DEFAULT_LOGIN_REDIRECT, } from '${features.internationalization ? '@/app/[locale]/lib/routes' : '@/lib/routes'}';` : ''} ${features.internationalization ? `// Create internationalization middleware with specified locales and settings const intlMiddleware = createMiddleware({ locales, localePrefix: 'as-needed', defaultLocale: 'en' });` : ''} // Function to check if the pathname matches any of the routes in the provided list const isRouteMatched = (pathname: string, routes: string[]) => { return routes.some(route => pathname === route || pathname.startsWith(route + '/')); }; ${features.authentication ? `const authMiddleware = auth(async (req) => { const { nextUrl } = req; const isLoggedIn = !!req.auth; const pathname = removeLocalePrefix(nextUrl.pathname); const isApiRoute = isRouteMatched(pathname, API_AUTH_PREFIX); const isAuthRoute = isRouteMatched(pathname, AUTH_ROUTES); const isPublicRoute = isRouteMatched(pathname, PUBLIC_ROUTES); const isProtectedRoute = isRouteMatched(pathname, PROTECTED_ROUTES); // Handle different route scenarios if (isApiRoute) { return; } if (isAuthRoute && isLoggedIn) { return Response.redirect(new URL(DEFAULT_LOGIN_REDIRECT, nextUrl)); } if (!isLoggedIn && isProtectedRoute) { return NextResponse.redirect(new URL(LOGIN, nextUrl)); } ${features.internationalization ? `// Apply internationalization middleware for all routes return intlMiddleware(req);` : `return NextResponse.next();`} });` : ''} // Main middleware function to handle requests export default function middleware(req: NextRequest) { ${features.internationalization && features.authentication ? ` // Apply authentication middleware first, then internationalization return (authMiddleware as any)(req); ` : features.internationalization ? ` // Apply internationalization middleware for all requests return intlMiddleware(req); ` : ` const { pathname } = req.nextUrl; const cleanPathname = removeLocalePrefix(pathname); const isPublicPage = isRouteMatched(cleanPathname, PUBLIC_ROUTES); if (isPublicPage) { return NextResponse.next(); } else { ${features.authentication ? `return (authMiddleware as any)(req);` : `return NextResponse.next();`} } `} } // Configuration for the middleware matcher export const config = { matcher: [ '/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)', '/(api|trpc)(.*)', ], };`; fs.writeFileSync(path.join(projectPath, 'src/middleware.ts'), middlewareTs); } } // Add authentication feature async function addAuthentication(projectPath, features) { console.log(' 🔐 Adding authentication...'); // Create auth configuration const authConfig = `import { NextAuthOptions } from "next-auth"; import CredentialsProvider from "next-auth/providers/credentials"; import GoogleProvider from "next-auth/providers/google"; ${features.database ? `import { PrismaAdapter } from "@auth/prisma-adapter"; import { db } from "@/lib/db";` : ''} import { PasswordUtils } from "@/lib/utils"; export const authConfig: NextAuthOptions = { ${features.database ? 'adapter: PrismaAdapter(db),' : ''} providers: [ GoogleProvider({ clientId: process.env.AUTH_GOOGLE_ID!, clientSecret: process.env.AUTH_GOOGLE_SECRET!, }), CredentialsProvider({ name: "credentials", credentials: { username: { label: "Username", type: "text" }, password: { label: "Password", type: "password" } }, async authorize(credentials) { if (!credentials?.username || !credentials?.password) { return null; } try { ${features.database ? ` const user = await db.user.findFirst({ where: { OR: [ { email: credentials.username }, { username: credentials.username }, { phone: credentials.username } ] } }); if (!user || !user.password) { return null; } const isPasswordValid = await PasswordUtils.comparePassword( credentials.password, user.password ); if (!isPasswordValid) { return null; } return { id: user.id, name: user.name, email: user.email, username: user.username, phone: user.phone, role: user.role, is_active: user.is_active, is_superuser: user.is_superuser, plan: user.plan, emailVerified: user.emailVerif