diff --git a/src/generate.js b/src/generate.js
index 5b20be7..ac1e2f9 100644
--- a/src/generate.js
+++ b/src/generate.js
@@ -6,6 +6,7 @@ import tiged from 'tiged';
import Handlebars from 'handlebars';
import { execSync } from 'child_process';
import { resolveDependencies } from './dependencies.js';
+import { generateNavigation } from './navigation.js';
// Register custom Handlebars helpers
Handlebars.registerHelper('eq', function (a, b) {
@@ -101,7 +102,10 @@ export async function generateProject(config) {
const configFilePath = path.join(projectPath, 'opusify.config.json');
fs.writeFileSync(configFilePath, JSON.stringify(config, null, 2));
- // 5. Resolve dynamic dependencies based on user choices
+ // 5. Generate dynamic navigation based on navCount
+ generateNavigation(projectPath, config);
+
+ // 6. Resolve dynamic dependencies based on user choices
resolveDependencies(projectPath, config);
// 6. AUTOMATION PHASE: Install Dependencies
diff --git a/src/navigation.js b/src/navigation.js
new file mode 100644
index 0000000..41dd73d
--- /dev/null
+++ b/src/navigation.js
@@ -0,0 +1,157 @@
+import fs from 'fs';
+import path from 'path';
+
+// Page pools for each template — ordered by priority
+const PAGE_POOLS = {
+ portfolio: [
+ { label: 'Home', href: '/', slug: null },
+ { label: 'About', href: '/about', slug: 'about' },
+ { label: 'Projects', href: '/projects', slug: 'projects' },
+ { label: 'Skills', href: '/skills', slug: 'skills' },
+ { label: 'Contact', href: '/contact', slug: 'contact' },
+ { label: 'Blog', href: '/blog', slug: 'blog' },
+ { label: 'Testimonials', href: '/testimonials', slug: 'testimonials' },
+ { label: 'Resume', href: '/resume', slug: 'resume' },
+ { label: 'Services', href: '/services', slug: 'services' },
+ ],
+ ecommerce: [
+ { label: 'Home', href: '/', slug: null },
+ { label: 'Products', href: '/products', slug: 'products' },
+ { label: 'Cart', href: '/cart', slug: 'cart' },
+ { label: 'Account', href: '/account', slug: 'account' },
+ { label: 'Wishlist', href: '/wishlist', slug: 'wishlist' },
+ { label: 'Orders', href: '/orders', slug: 'orders' },
+ { label: 'Categories', href: '/categories', slug: 'categories' },
+ { label: 'Search', href: '/search', slug: 'search' },
+ { label: 'Support', href: '/support', slug: 'support' },
+ ],
+ school: [
+ { label: 'Dashboard', href: '/', slug: null },
+ { label: 'Students', href: '/students', slug: 'students' },
+ { label: 'Courses', href: '/courses', slug: 'courses' },
+ { label: 'Grades', href: '/grades', slug: 'grades' },
+ { label: 'Attendance', href: '/attendance', slug: 'attendance' },
+ { label: 'Schedule', href: '/schedule', slug: 'schedule' },
+ { label: 'Payments', href: '/payments', slug: 'payments' },
+ { label: 'Reports', href: '/reports', slug: 'reports' },
+ { label: 'Staff', href: '/staff', slug: 'staff' },
+ ],
+ saas: [
+ { label: 'Dashboard', href: '/', slug: null },
+ { label: 'Analytics', href: '/analytics', slug: 'analytics' },
+ { label: 'Users', href: '/users', slug: 'users' },
+ { label: 'Billing', href: '/billing', slug: 'billing' },
+ { label: 'Settings', href: '/settings', slug: 'settings' },
+ { label: 'Reports', href: '/reports', slug: 'reports' },
+ { label: 'Integrations', href: '/integrations', slug: 'integrations' },
+ { label: 'Team', href: '/team', slug: 'team' },
+ { label: 'Support', href: '/support', slug: 'support' },
+ ],
+ blog: [
+ { label: 'Home', href: '/', slug: null },
+ { label: 'Articles', href: '/articles', slug: 'articles' },
+ { label: 'Categories', href: '/categories', slug: 'categories' },
+ { label: 'Authors', href: '/authors', slug: 'authors' },
+ { label: 'Newsletter', href: '/newsletter', slug: 'newsletter' },
+ { label: 'About', href: '/about', slug: 'about' },
+ { label: 'Tags', href: '/tags', slug: 'tags' },
+ { label: 'Archive', href: '/archive', slug: 'archive' },
+ { label: 'Contact', href: '/contact', slug: 'contact' },
+ ],
+};
+
+function generateNavTs(selectedPages) {
+ return `export interface NavItem {
+ label: string;
+ href: string;
+}
+
+export const navLinks: NavItem[] = [
+${selectedPages.map((p) => ` { label: '${p.label}', href: '${p.href}' },`).join('\n')}
+];
+`;
+}
+
+function generatePageComponent(page, config) {
+ return `export default function ${page.label.replace(/\s+/g, '')}Page() {
+ return (
+
+
+
${page.label}
+
+ This is the ${page.label} page for ${config.projectName}.
+
+
+
+ Content for this page will be added here. This placeholder was generated
+ by Opusify CLI based on your navigation configuration.
+
+
+
+
+ );
+}
+`;
+}
+
+function generateVitePageComponent(page, config) {
+ return `export default function ${page.label.replace(/\s+/g, '')}() {
+ return (
+
+
+
${page.label}
+
+ This is the ${page.label} page for ${config.projectName}.
+
+
+
+ Content for this page will be added here. This placeholder was generated
+ by Opusify CLI based on your navigation configuration.
+
+
+
+
+ );
+}
+`;
+}
+
+export function generateNavigation(projectPath, config) {
+ const pool = PAGE_POOLS[config.template];
+ if (!pool) return;
+
+ const navCount = Math.min(Math.max(config.navCount || 5, 3), 9);
+ const selectedPages = pool.slice(0, navCount);
+ const isVite = config.architecture === 'vite-react';
+
+ // 1. Generate lib/nav.ts (or src/lib/nav.ts for Vite)
+ const navDir = isVite
+ ? path.join(projectPath, 'src', 'lib')
+ : path.join(projectPath, 'lib');
+
+ if (!fs.existsSync(navDir)) fs.mkdirSync(navDir, { recursive: true });
+ fs.writeFileSync(path.join(navDir, 'nav.ts'), generateNavTs(selectedPages));
+
+ // 2. Generate route/page files for pages that don't already exist
+ for (const page of selectedPages) {
+ if (!page.slug) continue; // Skip home page — already exists as page.tsx
+
+ if (isVite) {
+ const pagePath = path.join(projectPath, 'src', 'pages', `${page.label.replace(/\s+/g, '')}.tsx`);
+ if (!fs.existsSync(pagePath)) {
+ const dir = path.dirname(pagePath);
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
+ fs.writeFileSync(pagePath, generateVitePageComponent(page, config));
+ }
+ } else {
+ const routeDir = path.join(projectPath, 'app', page.slug);
+ const routeFile = path.join(routeDir, 'page.tsx');
+ if (!fs.existsSync(routeFile)) {
+ if (!fs.existsSync(routeDir)) fs.mkdirSync(routeDir, { recursive: true });
+ fs.writeFileSync(routeFile, generatePageComponent(page, config));
+ }
+ }
+ }
+
+ return selectedPages;
+}
diff --git a/templates/blog/nextjs-monolith/app/layout.tsx b/templates/blog/nextjs-monolith/app/layout.tsx
index 297b2c1..167e16a 100644
--- a/templates/blog/nextjs-monolith/app/layout.tsx
+++ b/templates/blog/nextjs-monolith/app/layout.tsx
@@ -1,6 +1,7 @@
import type { Metadata } from 'next';
import Link from 'next/link';
import './globals.css';
+import { navLinks } from '../lib/nav';
import AnimationProvider from '../components/AnimationProvider';
{{#if (eq design "Dark Terminal")}}
import { Terminal } from 'lucide-react';
@@ -11,13 +12,6 @@ export const metadata: Metadata = {
description: 'A {{variant}} blog built with Opusify CLI.',
};
-const navLinks = [
- { label: 'Home', href: '/' },
- { label: 'Articles', href: '/articles' },
- { label: 'Categories', href: '/categories' },
- { label: 'Authors', href: '/authors' },
-];
-
function Navbar() {
return (
@@ -40,11 +34,9 @@ function Navbar() {
))}
-
-
-
+
);
diff --git a/templates/ecommerce/nextjs-monolith/app/layout.tsx b/templates/ecommerce/nextjs-monolith/app/layout.tsx
index 23d3441..c9fa802 100644
--- a/templates/ecommerce/nextjs-monolith/app/layout.tsx
+++ b/templates/ecommerce/nextjs-monolith/app/layout.tsx
@@ -1,6 +1,7 @@
import type { Metadata } from 'next';
import Link from 'next/link';
import './globals.css';
+import { navLinks } from '../lib/nav';
import AnimationProvider from '../components/AnimationProvider';
{{#if (eq design "Dark Terminal")}}
import { Terminal } from 'lucide-react';
@@ -11,13 +12,6 @@ export const metadata: Metadata = {
description: 'A {{variant}} e-commerce store built with Opusify CLI.',
};
-const navLinks = [
- { label: 'Home', href: '/' },
- { label: 'Products', href: '/products' },
- { label: 'Cart', href: '/cart' },
- { label: 'Account', href: '/account' },
-];
-
function Navbar() {
return (
diff --git a/templates/portfolio/nextjs-monolith/app/layout.tsx b/templates/portfolio/nextjs-monolith/app/layout.tsx
index dcc7872..e77f3b2 100644
--- a/templates/portfolio/nextjs-monolith/app/layout.tsx
+++ b/templates/portfolio/nextjs-monolith/app/layout.tsx
@@ -1,5 +1,7 @@
import type { Metadata } from 'next';
+import Link from 'next/link';
import './globals.css';
+import { navLinks } from '../lib/nav';
import AnimationProvider from '../components/AnimationProvider';
{{#if (eq design "Dark Terminal")}}
import { Terminal } from 'lucide-react';
@@ -10,6 +12,33 @@ export const metadata: Metadata = {
description: 'A {{variant}} portfolio built with Opusify CLI.',
};
+function Navbar() {
+ return (
+
+
+
+ );
+}
+
export default function RootLayout({
children,
}: {
@@ -18,12 +47,7 @@ export default function RootLayout({
return (
- {{#if (eq design "Dark Terminal")}}
-
- {{/if}}
+
{children}
diff --git a/templates/saas/nextjs-monolith/app/layout.tsx b/templates/saas/nextjs-monolith/app/layout.tsx
index 538ee7f..4e40e07 100644
--- a/templates/saas/nextjs-monolith/app/layout.tsx
+++ b/templates/saas/nextjs-monolith/app/layout.tsx
@@ -1,6 +1,7 @@
import type { Metadata } from 'next';
import Link from 'next/link';
import './globals.css';
+import { navLinks } from '../lib/nav';
import AnimationProvider from '../components/AnimationProvider';
{{#if (eq design "Dark Terminal")}}
import { Terminal } from 'lucide-react';
@@ -11,14 +12,6 @@ export const metadata: Metadata = {
description: 'A {{variant}} SaaS dashboard built with Opusify CLI.',
};
-const navLinks = [
- { label: 'Dashboard', href: '/' },
- { label: 'Analytics', href: '/analytics' },
- { label: 'Users', href: '/users' },
- { label: 'Billing', href: '/billing' },
- { label: 'Settings', href: '/settings' },
-];
-
function Navbar() {
return (
@@ -41,10 +34,8 @@ function Navbar() {
))}
-
-
- A
-
+
+ A
diff --git a/templates/saas/vite-react/src/components/Layout.tsx b/templates/saas/vite-react/src/components/Layout.tsx
index d3f2553..0ee2fdd 100644
--- a/templates/saas/vite-react/src/components/Layout.tsx
+++ b/templates/saas/vite-react/src/components/Layout.tsx
@@ -1,12 +1,5 @@
import { Link, Outlet } from 'react-router-dom';
-
-const navLinks = [
- { label: 'Dashboard', href: '/' },
- { label: 'Analytics', href: '/analytics' },
- { label: 'Users', href: '/users' },
- { label: 'Billing', href: '/billing' },
- { label: 'Settings', href: '/settings' },
-];
+import { navLinks } from '../lib/nav';
function Navbar() {
return (
diff --git a/templates/school/nextjs-monolith/app/layout.tsx b/templates/school/nextjs-monolith/app/layout.tsx
index 2bee723..94ef914 100644
--- a/templates/school/nextjs-monolith/app/layout.tsx
+++ b/templates/school/nextjs-monolith/app/layout.tsx
@@ -1,6 +1,7 @@
import type { Metadata } from 'next';
import Link from 'next/link';
import './globals.css';
+import { navLinks } from '../lib/nav';
import AnimationProvider from '../components/AnimationProvider';
{{#if (eq design "Dark Terminal")}}
import { Terminal } from 'lucide-react';
@@ -11,15 +12,6 @@ export const metadata: Metadata = {
description: 'A {{variant}} school management system built with Opusify CLI.',
};
-const navLinks = [
- { label: 'Dashboard', href: '/' },
- { label: 'Students', href: '/students' },
- { label: 'Courses', href: '/courses' },
- { label: 'Grades', href: '/grades' },
- { label: 'Attendance', href: '/attendance' },
- { label: 'Schedule', href: '/schedule' },
-];
-
function Navbar() {
return (