UNPKG

@asgerami/create-zemenay-blog

Version:

Create a new blog with Zemenay Blog in seconds

961 lines (831 loc) 30.5 kB
#!/usr/bin/env node const { execSync } = require("child_process"); const fs = require("fs"); const path = require("path"); const chalk = require("chalk"); const inquirer = require("inquirer"); const packageJson = require("./package.json"); console.log( chalk.cyan.bold(` ╔══════════════════════════════════════╗ ║ ║ ║ 🚀 Create Zemenay Blog ║ ║ ║ ║ The fastest way to create a ║ ║ professional blog system ║ ║ ║ ╚══════════════════════════════════════╝ `) ); console.log(chalk.gray(`v${packageJson.version}\n`)); async function main() { try { const projectName = process.argv[2] || (await getProjectName()); // Get customization options const config = await getCustomizationOptions(); console.log( chalk.blue(`\n📦 Creating your blog: ${chalk.bold(projectName)}\n`) ); // Step 1: Create Next.js project await createNextProject(projectName); // Step 2: Install Zemenay Blog await installZemenayBlog(projectName); // Step 3: Setup project structure with customizations await setupProjectStructure(projectName, config); // Step 4: Show success message showSuccessMessage(projectName, config); } catch (error) { console.error(chalk.red(`\n❌ Error: ${error.message}`)); process.exit(1); } } async function getProjectName() { const { projectName } = await inquirer.prompt([ { type: "input", name: "projectName", message: "What is your project name?", default: "my-zemenay-blog", validate: (input) => { if (!input.trim()) { return "Project name is required"; } if (fs.existsSync(input)) { return `Directory ${input} already exists`; } return true; }, }, ]); return projectName; } async function getCustomizationOptions() { console.log(chalk.cyan("\n🎨 Let's customize your blog!\n")); const config = await inquirer.prompt([ { type: "input", name: "blogTitle", message: "What is your blog title?", default: "My Awesome Blog", }, { type: "input", name: "blogDescription", message: "Blog description:", default: "A modern blog powered by Zemenay Blog", }, { type: "input", name: "authorName", message: "Author name:", default: "Blog Author", }, { type: "list", name: "theme", message: "Choose a color theme:", choices: [ { name: "🔵 Blue (Default)", value: "blue" }, { name: "🟢 Green", value: "green" }, { name: "🟣 Purple", value: "purple" }, { name: "🔴 Red", value: "red" }, { name: "🟠 Orange", value: "orange" }, { name: "🟡 Yellow", value: "yellow" }, ], default: "blue", }, { type: "list", name: "layout", message: "Choose homepage layout:", choices: [ { name: "🎨 Modern Gradient (Default)", value: "gradient" }, { name: "📰 Classic Blog", value: "classic" }, { name: "🚀 Minimal", value: "minimal" }, { name: "💼 Professional", value: "professional" }, ], default: "gradient", }, { type: "confirm", name: "includeAnalytics", message: "Include analytics dashboard?", default: true, }, { type: "confirm", name: "includeDarkMode", message: "Include dark/light theme toggle?", default: true, }, { type: "input", name: "primaryFont", message: "Primary font family (or press Enter for default):", default: "Inter", }, ]); return config; } async function createNextProject(projectName) { console.log(chalk.blue("🏗️ Creating Next.js project...")); try { execSync( `npx create-next-app@latest ${projectName} --typescript --tailwind --eslint --app --src-dir --import-alias "@/*" --yes`, { stdio: "pipe" } ); console.log(chalk.green("✅ Next.js project created")); } catch (error) { throw new Error("Failed to create Next.js project"); } } async function installZemenayBlog(projectName) { console.log(chalk.blue("📦 Installing Zemenay Blog...")); process.chdir(projectName); try { console.log( chalk.gray( "Running: npm install @asgerami/zemenay-blog --legacy-peer-deps" ) ); execSync("npm install @asgerami/zemenay-blog --legacy-peer-deps", { stdio: "inherit", }); console.log(chalk.green("✅ Zemenay Blog installed")); } catch (error) { console.error(chalk.red("npm install failed with error:"), error.message); throw new Error(`Failed to install Zemenay Blog: ${error.message}`); } } async function setupProjectStructure(projectName, config) { console.log(chalk.blue("📁 Setting up project structure...")); // Create environment file with custom values const envContent = `# Zemenay Blog Configuration NEXT_PUBLIC_SUPABASE_URL=your_supabase_url_here NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key_here # Custom Blog Configuration NEXT_PUBLIC_BLOG_TITLE=${config.blogTitle} NEXT_PUBLIC_BLOG_DESCRIPTION=${config.blogDescription} NEXT_PUBLIC_BLOG_AUTHOR=${config.authorName} `; fs.writeFileSync(".env.local", envContent); // Generate theme colors based on user selection const themeColors = getThemeColors(config.theme); // Update globals.css with custom theme const globalsCss = `@tailwind base; @tailwind components; @tailwind utilities; /* Import Zemenay Blog styles */ @import '@asgerami/zemenay-blog/dist/styles.css'; /* Custom theme variables */ :root { --accent-color: ${themeColors.accent}; --success-color: ${themeColors.success}; --error-color: ${themeColors.error}; --font-family: '${config.primaryFont}', -apple-system, BlinkMacSystemFont, sans-serif; } /* Custom styles */ body { font-family: var(--font-family); } `; fs.writeFileSync("src/app/globals.css", globalsCss); // Create layout.tsx with custom metadata const layoutContent = `import { ThemeScript } from '@asgerami/zemenay-blog'; import './globals.css'; export const metadata = { title: '${config.blogTitle}', description: '${config.blogDescription}', authors: [{ name: '${config.authorName}' }], }; export default function RootLayout({ children, }: { children: React.ReactNode; }) { return ( <html lang="en"> <head> <ThemeScript /> </head> <body>{children}</body> </html> ); } `; fs.writeFileSync("src/app/layout.tsx", layoutContent); // Create homepage with custom layout const homeContent = generateHomepageLayout(config); fs.writeFileSync("src/app/page.tsx", homeContent); // Create blog pages fs.mkdirSync("src/app/blog", { recursive: true }); fs.mkdirSync("src/app/blog/[slug]", { recursive: true }); fs.mkdirSync("src/app/admin", { recursive: true }); fs.mkdirSync("src/app/admin/analytics", { recursive: true }); // Blog listing page const blogListContent = `import { BlogList, SearchWithFilters, ThemeToggle } from '@asgerami/zemenay-blog'; import Link from 'next/link'; export const metadata = { title: 'Blog Posts', description: 'Read our latest blog posts', }; export default function BlogPage() { return ( <div className="min-h-screen bg-gray-50 dark:bg-gray-900"> <div className="container mx-auto px-4 py-8"> <div className="flex justify-between items-center mb-8"> <div> <Link href="/" className="text-blue-600 hover:text-blue-700 mb-4 inline-block"> ← Back to Home </Link> <h1 className="text-4xl font-bold text-gray-900 dark:text-white"> Blog Posts </h1> </div> <ThemeToggle /> </div> <div className="mb-8"> <SearchWithFilters /> </div> <BlogList /> </div> </div> ); } `; fs.writeFileSync("src/app/blog/page.tsx", blogListContent); // Individual blog post page const blogPostContent = `import { BlogPost } from '@asgerami/zemenay-blog'; import Link from 'next/link'; export default function PostPage({ params }: { params: { slug: string } }) { return ( <div className="min-h-screen bg-gray-50 dark:bg-gray-900"> <div className="container mx-auto px-4 py-8"> <Link href="/blog" className="text-blue-600 hover:text-blue-700 mb-8 inline-block"> ← Back to Blog </Link> <BlogPost slug={params.slug} /> </div> </div> ); } `; fs.writeFileSync("src/app/blog/[slug]/page.tsx", blogPostContent); // Admin page const adminContent = `import { AdminPanel } from '@asgerami/zemenay-blog'; export const metadata = { title: 'Admin Panel', description: 'Manage your blog posts', }; export default function AdminPage() { return <AdminPanel />; } `; fs.writeFileSync("src/app/admin/page.tsx", adminContent); // Analytics page const analyticsContent = `import { AnalyticsDashboard, AdminLayout } from '@asgerami/zemenay-blog'; export const metadata = { title: 'Blog Analytics', description: 'View your blog analytics and insights', }; export default function AnalyticsPage() { return ( <AdminLayout title="📊 Blog Analytics" description="Comprehensive insights into your blog's performance and audience engagement." > <AnalyticsDashboard /> </AdminLayout> ); } `; fs.writeFileSync("src/app/admin/analytics/page.tsx", analyticsContent); // Create SQL setup files createSQLFiles(); console.log(chalk.green("✅ Project structure created")); } function getThemeColors(theme) { const themes = { blue: { accent: "#3b82f6", success: "#10b981", error: "#ef4444", }, green: { accent: "#10b981", success: "#059669", error: "#ef4444", }, purple: { accent: "#8b5cf6", success: "#10b981", error: "#ef4444", }, red: { accent: "#ef4444", success: "#10b981", error: "#dc2626", }, orange: { accent: "#f97316", success: "#10b981", error: "#ef4444", }, yellow: { accent: "#eab308", success: "#10b981", error: "#ef4444", }, }; return themes[theme] || themes.blue; } function createSQLFiles() { const sqlDir = "supabase-setup"; fs.mkdirSync(sqlDir, { recursive: true }); const sqlFiles = { "01-main-schema.sql": `-- Zemenay Blog Main Schema -- Run this first in your Supabase SQL Editor -- Create posts table CREATE TABLE IF NOT EXISTS posts ( id UUID DEFAULT gen_random_uuid() PRIMARY KEY, title TEXT NOT NULL, content TEXT NOT NULL, slug TEXT UNIQUE NOT NULL, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), featured_image_id UUID ); -- Create categories table CREATE TABLE IF NOT EXISTS categories ( id UUID DEFAULT gen_random_uuid() PRIMARY KEY, name TEXT NOT NULL, slug TEXT UNIQUE NOT NULL, description TEXT, color TEXT DEFAULT '#3b82f6', created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); -- Create tags table CREATE TABLE IF NOT EXISTS tags ( id UUID DEFAULT gen_random_uuid() PRIMARY KEY, name TEXT NOT NULL, slug TEXT UNIQUE NOT NULL, color TEXT DEFAULT '#10b981', created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); -- Create images table CREATE TABLE IF NOT EXISTS images ( id UUID DEFAULT gen_random_uuid() PRIMARY KEY, filename TEXT NOT NULL, original_name TEXT NOT NULL, file_path TEXT NOT NULL, file_size INTEGER NOT NULL, mime_type TEXT NOT NULL, alt_text TEXT, uploaded_by UUID, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); -- Enable RLS ALTER TABLE posts ENABLE ROW LEVEL SECURITY; ALTER TABLE categories ENABLE ROW LEVEL SECURITY; ALTER TABLE tags ENABLE ROW LEVEL SECURITY; ALTER TABLE images ENABLE ROW LEVEL SECURITY; -- Create policies for public read access CREATE POLICY "Allow public read access on posts" ON posts FOR SELECT USING (true); CREATE POLICY "Allow public read access on categories" ON categories FOR SELECT USING (true); CREATE POLICY "Allow public read access on tags" ON tags FOR SELECT USING (true); CREATE POLICY "Allow public read access on images" ON images FOR SELECT USING (true); -- Create policies for authenticated write access CREATE POLICY "Allow authenticated users to manage posts" ON posts FOR ALL USING (auth.role() = 'authenticated'); CREATE POLICY "Allow authenticated users to manage categories" ON categories FOR ALL USING (auth.role() = 'authenticated'); CREATE POLICY "Allow authenticated users to manage tags" ON tags FOR ALL USING (auth.role() = 'authenticated'); CREATE POLICY "Allow authenticated users to manage images" ON images FOR ALL USING (auth.role() = 'authenticated'); `, "02-junction-tables.sql": `-- Zemenay Blog Junction Tables -- Run this second -- Create junction tables CREATE TABLE IF NOT EXISTS post_categories ( id UUID DEFAULT gen_random_uuid() PRIMARY KEY, post_id UUID NOT NULL REFERENCES posts(id) ON DELETE CASCADE, category_id UUID NOT NULL REFERENCES categories(id) ON DELETE CASCADE, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), UNIQUE(post_id, category_id) ); CREATE TABLE IF NOT EXISTS post_tags ( id UUID DEFAULT gen_random_uuid() PRIMARY KEY, post_id UUID NOT NULL REFERENCES posts(id) ON DELETE CASCADE, tag_id UUID NOT NULL REFERENCES tags(id) ON DELETE CASCADE, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), UNIQUE(post_id, tag_id) ); -- Enable RLS and create policies ALTER TABLE post_categories ENABLE ROW LEVEL SECURITY; ALTER TABLE post_tags ENABLE ROW LEVEL SECURITY; CREATE POLICY "Allow public read access on post_categories" ON post_categories FOR SELECT USING (true); CREATE POLICY "Allow public read access on post_tags" ON post_tags FOR SELECT USING (true); CREATE POLICY "Allow authenticated users to manage post_categories" ON post_categories FOR ALL USING (auth.role() = 'authenticated'); CREATE POLICY "Allow authenticated users to manage post_tags" ON post_tags FOR ALL USING (auth.role() = 'authenticated'); `, "03-analytics-schema.sql": `-- Zemenay Blog Analytics Schema -- Run this third -- Analytics tables CREATE TABLE IF NOT EXISTS post_views ( id UUID DEFAULT gen_random_uuid() PRIMARY KEY, post_id UUID NOT NULL REFERENCES posts(id) ON DELETE CASCADE, visitor_id TEXT NOT NULL, user_agent TEXT, referrer TEXT, viewed_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); CREATE TABLE IF NOT EXISTS post_engagement ( id UUID DEFAULT gen_random_uuid() PRIMARY KEY, post_id UUID NOT NULL REFERENCES posts(id) ON DELETE CASCADE, visitor_id TEXT NOT NULL, event_type TEXT NOT NULL CHECK (event_type IN ('time_spent', 'scroll_depth', 'click', 'share')), value INTEGER NOT NULL, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); -- Create indexes for better performance CREATE INDEX IF NOT EXISTS idx_post_views_post_id ON post_views(post_id); CREATE INDEX IF NOT EXISTS idx_post_views_viewed_at ON post_views(viewed_at); CREATE INDEX IF NOT EXISTS idx_post_engagement_post_id ON post_engagement(post_id); -- Enable RLS and create policies ALTER TABLE post_views ENABLE ROW LEVEL SECURITY; ALTER TABLE post_engagement ENABLE ROW LEVEL SECURITY; CREATE POLICY "Allow public insert on post_views" ON post_views FOR INSERT WITH CHECK (true); CREATE POLICY "Allow public insert on post_engagement" ON post_engagement FOR INSERT WITH CHECK (true); CREATE POLICY "Allow public read on post_views" ON post_views FOR SELECT USING (true); CREATE POLICY "Allow public read on post_engagement" ON post_engagement FOR SELECT USING (true); `, "04-storage-setup.sql": `-- Zemenay Blog Storage Setup -- Run this fourth -- Create storage bucket for images INSERT INTO storage.buckets (id, name, public) VALUES ('blog-images', 'blog-images', true); -- Create storage policies CREATE POLICY "Allow public read access on blog images" ON storage.objects FOR SELECT USING (bucket_id = 'blog-images'); CREATE POLICY "Allow authenticated users to upload images" ON storage.objects FOR INSERT WITH CHECK (bucket_id = 'blog-images' AND auth.role() = 'authenticated'); CREATE POLICY "Allow authenticated users to delete images" ON storage.objects FOR DELETE USING (bucket_id = 'blog-images' AND auth.role() = 'authenticated'); `, "README.md": `# Supabase Setup for Zemenay Blog Follow these steps to set up your Supabase database: ## 1. Create Supabase Project - Go to [supabase.com](https://supabase.com) - Create a new project - Wait for it to be ready ## 2. Run SQL Files In your Supabase dashboard, go to the SQL Editor and run these files in order: 1. **01-main-schema.sql** - Creates main tables (posts, categories, tags, images) 2. **02-junction-tables.sql** - Creates junction tables for many-to-many relationships 3. **03-analytics-schema.sql** - Creates analytics tables for tracking 4. **04-storage-setup.sql** - Sets up image storage bucket ## 3. Get Your Credentials - Go to Settings > API in your Supabase dashboard - Copy your Project URL and anon public key - Update your .env.local file: \`\`\`env NEXT_PUBLIC_SUPABASE_URL=your_project_url_here NEXT_PUBLIC_SUPABASE_ANON_KEY=your_anon_key_here \`\`\` ## 4. Enable Authentication - Go to Authentication > Providers - Enable Google OAuth (recommended) - Add your domain to redirect URLs ## 5. Test Your Setup - Run \`npm run dev\` - Visit http://localhost:3000/admin - Try signing in and creating a post That's it! Your blog is ready to use. 🎉 `, }; Object.entries(sqlFiles).forEach(([filename, content]) => { fs.writeFileSync(path.join(sqlDir, filename), content); }); } function generateHomepageLayout(config) { const themeToggle = config.includeDarkMode ? "<ThemeToggle />" : ""; const analyticsLink = config.includeAnalytics ? ` <Link href="/admin/analytics" className="bg-purple-600 hover:bg-purple-700 text-white px-8 py-3 rounded-lg font-semibold transition-colors" > 📊 Analytics </Link>` : ""; const layouts = { gradient: `import Link from 'next/link'; ${ config.includeDarkMode ? "import { ThemeToggle } from '@asgerami/zemenay-blog';" : "" } export default function HomePage() { return ( <div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 dark:from-gray-900 dark:to-gray-800"> <div className="container mx-auto px-4 py-16"> <div className="text-center"> ${ config.includeDarkMode ? `<div className="flex justify-end mb-8"> <ThemeToggle /> </div>` : "" } <h1 className="text-5xl font-bold text-gray-900 dark:text-white mb-6"> ${config.blogTitle} </h1> <p className="text-xl text-gray-600 dark:text-gray-300 mb-12 max-w-2xl mx-auto"> ${config.blogDescription} </p> <div className="flex flex-col sm:flex-row gap-4 justify-center"> <Link href="/blog" className="bg-blue-600 hover:bg-blue-700 text-white px-8 py-3 rounded-lg font-semibold transition-colors" > 📖 View Blog </Link> <Link href="/admin" className="bg-green-600 hover:bg-green-700 text-white px-8 py-3 rounded-lg font-semibold transition-colors" > ⚙️ Admin Panel </Link>${analyticsLink} </div> <div className="mt-16 grid md:grid-cols-3 gap-8 max-w-4xl mx-auto"> <div className="bg-white dark:bg-gray-800 p-6 rounded-lg shadow-lg"> <div className="text-3xl mb-4">✍️</div> <h3 className="text-lg font-semibold mb-2">Rich Text Editor</h3> <p className="text-gray-600 dark:text-gray-300"> Create beautiful posts with our powerful rich text editor </p> </div> <div className="bg-white dark:bg-gray-800 p-6 rounded-lg shadow-lg"> <div className="text-3xl mb-4">📊</div> <h3 className="text-lg font-semibold mb-2">Analytics</h3> <p className="text-gray-600 dark:text-gray-300"> Track your blog's performance with detailed analytics </p> </div> <div className="bg-white dark:bg-gray-800 p-6 rounded-lg shadow-lg"> <div className="text-3xl mb-4">🎨</div> <h3 className="text-lg font-semibold mb-2">Customizable</h3> <p className="text-gray-600 dark:text-gray-300"> Fully customizable themes and components </p> </div> </div> </div> </div> </div> ); }`, classic: `import Link from 'next/link'; import { BlogList } from '@asgerami/zemenay-blog'; ${ config.includeDarkMode ? "import { ThemeToggle } from '@asgerami/zemenay-blog';" : "" } export default function HomePage() { return ( <div className="min-h-screen bg-white dark:bg-gray-900"> <header className="bg-gray-50 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700"> <div className="container mx-auto px-4 py-8"> <div className="flex justify-between items-center"> <div> <h1 className="text-3xl font-bold text-gray-900 dark:text-white"> ${config.blogTitle} </h1> <p className="text-gray-600 dark:text-gray-300 mt-2"> ${config.blogDescription} </p> </div> ${config.includeDarkMode ? "<ThemeToggle />" : ""} </div> <nav className="mt-8"> <div className="flex space-x-4"> <Link href="/blog" className="text-blue-600 hover:text-blue-700 font-medium"> Blog </Link> <Link href="/admin" className="text-blue-600 hover:text-blue-700 font-medium"> Admin </Link> ${ config.includeAnalytics ? `<Link href="/admin/analytics" className="text-blue-600 hover:text-blue-700 font-medium"> Analytics </Link>` : "" } </div> </nav> </div> </header> <main className="container mx-auto px-4 py-8"> <BlogList /> </main> </div> ); }`, minimal: `import Link from 'next/link'; ${ config.includeDarkMode ? "import { ThemeToggle } from '@asgerami/zemenay-blog';" : "" } export default function HomePage() { return ( <div className="min-h-screen bg-white dark:bg-gray-900 flex items-center justify-center"> <div className="text-center max-w-2xl mx-auto px-4"> ${ config.includeDarkMode ? `<div className="absolute top-8 right-8"> <ThemeToggle /> </div>` : "" } <h1 className="text-6xl font-light text-gray-900 dark:text-white mb-8"> ${config.blogTitle} </h1> <p className="text-lg text-gray-600 dark:text-gray-400 mb-12"> ${config.blogDescription} </p> <div className="space-y-4"> <Link href="/blog" className="block text-blue-600 hover:text-blue-700 text-lg font-medium" > Read the blog → </Link> <Link href="/admin" className="block text-gray-600 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300" > Admin panel </Link> ${ config.includeAnalytics ? `<Link href="/admin/analytics" className="block text-gray-600 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300" > Analytics </Link>` : "" } </div> </div> </div> ); }`, professional: `import Link from 'next/link'; ${ config.includeDarkMode ? "import { ThemeToggle } from '@asgerami/zemenay-blog';" : "" } export default function HomePage() { return ( <div className="min-h-screen bg-gray-50 dark:bg-gray-900"> <nav className="bg-white dark:bg-gray-800 shadow-sm"> <div className="container mx-auto px-4"> <div className="flex justify-between items-center h-16"> <div className="font-semibold text-xl text-gray-900 dark:text-white"> ${config.blogTitle} </div> <div className="flex items-center space-x-6"> <Link href="/blog" className="text-gray-700 hover:text-gray-900 dark:text-gray-300 dark:hover:text-white"> Blog </Link> <Link href="/admin" className="text-gray-700 hover:text-gray-900 dark:text-gray-300 dark:hover:text-white"> Admin </Link> ${ config.includeAnalytics ? `<Link href="/admin/analytics" className="text-gray-700 hover:text-gray-900 dark:text-gray-300 dark:hover:text-white"> Analytics </Link>` : "" } ${config.includeDarkMode ? "<ThemeToggle />" : ""} </div> </div> </div> </nav> <main> <section className="py-20"> <div className="container mx-auto px-4 text-center"> <h1 className="text-5xl font-bold text-gray-900 dark:text-white mb-6"> ${config.blogTitle} </h1> <p className="text-xl text-gray-600 dark:text-gray-300 mb-12 max-w-3xl mx-auto"> ${config.blogDescription} </p> <Link href="/blog" className="inline-block bg-blue-600 hover:bg-blue-700 text-white px-8 py-3 rounded-lg font-semibold transition-colors" > Explore Articles </Link> </div> </section> <section className="py-16 bg-white dark:bg-gray-800"> <div className="container mx-auto px-4"> <div className="grid md:grid-cols-3 gap-8"> <div className="text-center"> <div className="w-16 h-16 bg-blue-100 dark:bg-blue-900 rounded-full flex items-center justify-center mx-auto mb-4"> <span className="text-2xl">📝</span> </div> <h3 className="text-xl font-semibold mb-2">Professional Writing</h3> <p className="text-gray-600 dark:text-gray-300"> High-quality content with professional presentation </p> </div> <div className="text-center"> <div className="w-16 h-16 bg-green-100 dark:bg-green-900 rounded-full flex items-center justify-center mx-auto mb-4"> <span className="text-2xl">📊</span> </div> <h3 className="text-xl font-semibold mb-2">Data-Driven Insights</h3> <p className="text-gray-600 dark:text-gray-300"> Track performance and understand your audience </p> </div> <div className="text-center"> <div className="w-16 h-16 bg-purple-100 dark:bg-purple-900 rounded-full flex items-center justify-center mx-auto mb-4"> <span className="text-2xl">🚀</span> </div> <h3 className="text-xl font-semibold mb-2">Modern Technology</h3> <p className="text-gray-600 dark:text-gray-300"> Built with the latest web technologies for optimal performance </p> </div> </div> </div> </section> </main> </div> ); }`, }; return layouts[config.layout] || layouts.gradient; } function showSuccessMessage(projectName, config) { console.log( chalk.green.bold(` 🎉 Success! Your Zemenay Blog has been created! 📁 Project: ${projectName} 🎨 Theme: ${config.theme} | Layout: ${config.layout} `) ); console.log( chalk.yellow(` ✨ Your customizations: ${chalk.gray("→")} Blog Title: ${config.blogTitle} ${chalk.gray("→")} Description: ${config.blogDescription} ${chalk.gray("→")} Author: ${config.authorName} ${chalk.gray("→")} Theme: ${config.theme} ${chalk.gray("→")} Layout: ${config.layout} ${chalk.gray("→")} Font: ${config.primaryFont} ${chalk.gray("→")} Analytics: ${ config.includeAnalytics ? "Enabled" : "Disabled" } ${chalk.gray("→")} Dark Mode: ${ config.includeDarkMode ? "Enabled" : "Disabled" } `) ); console.log( chalk.cyan(` 📋 Next steps: 1. 🗄️ Set up Supabase database: ${chalk.gray("→")} Go to supabase.com and create a project ${chalk.gray("→")} Run the SQL files in supabase-setup/ directory ${chalk.gray("→")} Update .env.local with your credentials 2. 🚀 Start development server: ${chalk.gray("→")} cd ${projectName} ${chalk.gray("→")} npm run dev 3. 🌐 Visit your blog: ${chalk.gray("→")} Homepage: http://localhost:3000 ${chalk.gray("→")} Blog: http://localhost:3000/blog ${chalk.gray("→")} Admin: http://localhost:3000/admin ${ config.includeAnalytics ? `${chalk.gray("→")} Analytics: http://localhost:3000/admin/analytics` : "" } 4. ✍️ Create your first post: ${chalk.gray("→")} Go to /admin and sign in ${chalk.gray("→")} Click "New Post" to start writing `) ); console.log( chalk.magenta(` 📚 Documentation: https://github.com/zemenay/zemenay-blog 🆘 Support: https://github.com/zemenay/zemenay-blog/issues Happy blogging with your ${config.theme} ${config.layout} blog! 🚀 `) ); } main().catch((error) => { console.error(chalk.red(`\n❌ Error: ${error.message}`)); process.exit(1); });