@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
JavaScript
#!/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