@asgerami/create-zemenay-blog
Version:
Create a new blog with Zemenay Blog in seconds
961 lines (831 loc) • 30.5 kB
JavaScript
#!/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);
});