@infuro/cms-core 1.0.26 → 1.0.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.cjs CHANGED
@@ -85,6 +85,8 @@ export const getAuthenticatedUser = helpers.getAuthenticatedUser;
85
85
  "src/lib/cms.ts": `import {
86
86
  createCmsApp,
87
87
  localStoragePlugin,
88
+ blogGeneratorPlugin,
89
+ socialMediaPlugin,
88
90
  type CmsApp,
89
91
  } from '@infuro/cms-core';
90
92
  import { getDataSourceInitialized } from './data-source';
@@ -99,6 +101,8 @@ export async function getCms(): Promise<CmsApp> {
99
101
  config: process.env as unknown as Record<string, string>,
100
102
  plugins: [
101
103
  localStoragePlugin({ dir: 'public/uploads' }),
104
+ blogGeneratorPlugin(),
105
+ socialMediaPlugin(),
102
106
  ],
103
107
  });
104
108
  return cmsPromise;
@@ -365,21 +369,35 @@ main().catch((e) => {
365
369
  });
366
370
  `,
367
371
  "scripts/migration-datasource.cjs": `/**
368
- * Data source for TypeORM CLI (migration:generate). Resolves @infuro/cms-core from project root (works with npm link).
372
+ * Data source for TypeORM CLI (migration:run / migration:revert).
373
+ * Loads app migrations plus core migrations from node_modules/@infuro/cms-core/dist/migrations.
369
374
  */
370
375
  require('reflect-metadata');
371
376
  require('dotenv/config');
377
+ const fs = require('fs');
372
378
  const path = require('path');
373
379
  const { DataSource } = require('typeorm');
374
- const coreEntry = path.resolve(__dirname, '..', 'node_modules', '@infuro', 'cms-core', 'dist', 'index.cjs');
380
+
381
+ const root = path.resolve(__dirname, '..');
382
+ const coreEntry = path.join(root, 'node_modules', '@infuro', 'cms-core', 'dist', 'index.cjs');
375
383
  const { CMS_ENTITY_MAP } = require(coreEntry);
376
384
 
385
+ function migrationGlobs() {
386
+ const globs = [path.join(root, 'src', 'migrations', '*.ts')];
387
+ const coreDist = path.join(root, 'node_modules', '@infuro', 'cms-core', 'dist', 'migrations');
388
+ if (fs.existsSync(coreDist)) {
389
+ globs.push(path.join(coreDist, '*.js'));
390
+ globs.push(path.join(coreDist, '*.ts'));
391
+ }
392
+ return globs;
393
+ }
394
+
377
395
  module.exports = new DataSource({
378
396
  type: 'postgres',
379
397
  url: process.env.DATABASE_URL,
380
398
  entities: Object.values(CMS_ENTITY_MAP),
381
399
  synchronize: false,
382
- migrations: ['src/migrations/*.ts'],
400
+ migrations: migrationGlobs(),
383
401
  });
384
402
  `,
385
403
  "scripts/run-migrations.ts": `/**
@@ -681,12 +699,12 @@ function patchNextConfig(root, dryRun2, log) {
681
699
  if (content.includes("serverExternalPackages")) {
682
700
  content = content.replace(
683
701
  /(serverExternalPackages:\s*\[)/,
684
- "$1'@infuro/cms-core', 'typeorm', "
702
+ "$1'@infuro/cms-core', 'typeorm', 'rss-parser', "
685
703
  );
686
704
  } else {
687
705
  content = content.replace(
688
706
  /(const nextConfig\s*=\s*\{|module\.exports\s*=\s*\{)/,
689
- "$1\n serverExternalPackages: ['@infuro/cms-core', 'typeorm'],"
707
+ "$1\n serverExternalPackages: ['@infuro/cms-core', 'typeorm', 'rss-parser'],"
690
708
  );
691
709
  }
692
710
  if (dryRun2) {
package/dist/cli.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\r\n/**\r\n * @infuro/cms-core init CLI\r\n * Usage: npx @infuro/cms-core init [--force] [--dry-run] [--no-deps] [--no-patch-config]\r\n */\r\nimport fs from 'fs';\r\nimport path from 'path';\r\n\r\nconst TEMPLATES = {\r\n 'src/lib/data-source.ts': `import 'reflect-metadata';\r\nimport path from 'path';\r\nimport { createRequire } from 'module';\r\nimport { DataSource } from 'typeorm';\r\nimport { CMS_ENTITY_MAP } from '@infuro/cms-core';\r\n\r\nconst require = createRequire(import.meta.url);\r\nconst coreDir = path.dirname(require.resolve('@infuro/cms-core'));\r\n\r\nlet dataSource: DataSource | null = null;\r\n\r\nexport function getDataSource(): DataSource {\r\n if (!dataSource) {\r\n dataSource = new DataSource({\r\n type: 'postgres',\r\n url: process.env.DATABASE_URL,\r\n entities: Object.values(CMS_ENTITY_MAP),\r\n synchronize: false,\r\n ...(process.env.TYPEORM_CLI && {\r\n migrations: [\r\n path.join(coreDir, 'migrations', '*.ts'),\r\n path.join(process.cwd(), 'src', 'migrations', '*.ts'),\r\n ],\r\n }),\r\n });\r\n }\r\n return dataSource;\r\n}\r\n\r\nexport async function getDataSourceInitialized(): Promise<DataSource> {\r\n const ds = getDataSource();\r\n if (!ds.isInitialized) await ds.initialize();\r\n return ds;\r\n}\r\n\r\nexport default getDataSource;\r\n`,\r\n\r\n 'src/lib/auth-helpers.ts': `import { getServerSession } from 'next-auth';\r\nimport { NextResponse } from 'next/server';\r\nimport { createAuthHelpers } from '@infuro/cms-core/auth';\r\n\r\nconst helpers = createAuthHelpers(\r\n async () => {\r\n const s = await getServerSession();\r\n return s ? { user: s.user } : null;\r\n },\r\n NextResponse\r\n);\r\n\r\nexport const requireAuth = helpers.requireAuth;\r\nexport const requirePermission = helpers.requirePermission;\r\nexport const requireEntityPermission = helpers.requireEntityPermission;\r\nexport const requireAdminAccess = helpers.requireAdminAccess;\r\nexport const getAuthenticatedUser = helpers.getAuthenticatedUser;\r\n`,\r\n\r\n 'src/lib/cms.ts': `import {\r\n createCmsApp,\r\n localStoragePlugin,\r\n type CmsApp,\r\n} from '@infuro/cms-core';\r\nimport { getDataSourceInitialized } from './data-source';\r\n\r\nlet cmsPromise: Promise<CmsApp> | null = null;\r\n\r\nexport async function getCms(): Promise<CmsApp> {\r\n if (cmsPromise) return cmsPromise;\r\n const dataSource = await getDataSourceInitialized();\r\n cmsPromise = createCmsApp({\r\n dataSource,\r\n config: process.env as unknown as Record<string, string>,\r\n plugins: [\r\n localStoragePlugin({ dir: 'public/uploads' }),\r\n ],\r\n });\r\n return cmsPromise;\r\n}\r\n`,\r\n\r\n 'src/app/api/[[...path]]/route.ts': `import { NextResponse } from 'next/server';\r\nimport { getServerSession } from 'next-auth';\r\nimport { createCmsApiHandler } from '@infuro/cms-core/api';\r\nimport { CMS_ENTITY_MAP } from '@infuro/cms-core';\r\nimport { getDataSourceInitialized } from '@/lib/data-source';\r\nimport {\r\n requireAuth,\r\n requireAdminAccess,\r\n requireEntityPermission,\r\n getAuthenticatedUser,\r\n} from '@/lib/auth-helpers';\r\nimport { getCms } from '@/lib/cms';\r\nimport bcrypt from 'bcryptjs';\r\n\r\nconst baseUrl = process.env.NEXTAUTH_URL || 'http://localhost:3000';\r\n\r\nasync function requireAdminApiAuth(req: Request) {\r\n const a = await requireAuth(req);\r\n if (a) return a;\r\n return requireAdminAccess(req);\r\n}\r\n\r\nlet handlerPromise: Promise<ReturnType<typeof createCmsApiHandler>> | null = null;\r\n\r\nasync function getHandler() {\r\n if (!handlerPromise) {\r\n const dataSource = await getDataSourceInitialized();\r\n handlerPromise = Promise.resolve(\r\n createCmsApiHandler({\r\n dataSource,\r\n entityMap: CMS_ENTITY_MAP,\r\n requireAuth: requireAdminApiAuth,\r\n requireEntityPermission,\r\n getSessionUser: getAuthenticatedUser,\r\n json: NextResponse.json.bind(NextResponse),\r\n getCms,\r\n userAuth: {\r\n dataSource,\r\n entityMap: CMS_ENTITY_MAP,\r\n json: NextResponse.json.bind(NextResponse),\r\n baseUrl,\r\n hashPassword: (p) => Promise.resolve(bcrypt.hashSync(p, 12)),\r\n comparePassword: (p, h) => Promise.resolve(bcrypt.compareSync(p, h)),\r\n resetExpiryHours: 1,\r\n getSession: () =>\r\n getServerSession().then((s) => (s ? { user: s.user } : null)),\r\n },\r\n dashboard: {\r\n dataSource,\r\n entityMap: CMS_ENTITY_MAP,\r\n json: NextResponse.json.bind(NextResponse),\r\n requireAuth: requireAdminApiAuth,\r\n requirePermission: requireAdminApiAuth,\r\n },\r\n upload: {\r\n json: NextResponse.json.bind(NextResponse),\r\n requireAuth: requireAdminApiAuth,\r\n storage: () => getCms().then((cms) => cms.getPlugin('storage')),\r\n localUploadDir: 'public/uploads',\r\n },\r\n blogBySlug: {\r\n dataSource,\r\n entityMap: CMS_ENTITY_MAP,\r\n json: NextResponse.json.bind(NextResponse),\r\n requireAuth: async () => null,\r\n },\r\n formBySlug: {\r\n dataSource,\r\n entityMap: CMS_ENTITY_MAP,\r\n json: NextResponse.json.bind(NextResponse),\r\n requireAuth: async () => null,\r\n },\r\n usersApi: {\r\n dataSource,\r\n entityMap: CMS_ENTITY_MAP,\r\n json: NextResponse.json.bind(NextResponse),\r\n requireAuth: requireAdminApiAuth,\r\n baseUrl,\r\n },\r\n userProfile: {\r\n dataSource,\r\n entityMap: CMS_ENTITY_MAP,\r\n json: NextResponse.json.bind(NextResponse),\r\n getSession: () =>\r\n getServerSession().then((s) => (s ? { user: s.user } : null)),\r\n },\r\n })\r\n );\r\n }\r\n return handlerPromise;\r\n}\r\n\r\nasync function handle(method: string, req: Request, context: { params: Promise<{ path?: string[] }> }) {\r\n try {\r\n const handler = await getHandler();\r\n const { path: pathSegments = [] } = await context.params;\r\n return handler.handle(method, pathSegments, req);\r\n } catch {\r\n return NextResponse.json({ error: 'Server Error' }, { status: 500 });\r\n }\r\n}\r\n\r\nexport async function GET(req: Request, ctx: { params: Promise<{ path?: string[] }> }) { return handle('GET', req, ctx); }\r\nexport async function POST(req: Request, ctx: { params: Promise<{ path?: string[] }> }) { return handle('POST', req, ctx); }\r\nexport async function PUT(req: Request, ctx: { params: Promise<{ path?: string[] }> }) { return handle('PUT', req, ctx); }\r\nexport async function PATCH(req: Request, ctx: { params: Promise<{ path?: string[] }> }) { return handle('PATCH', req, ctx); }\r\nexport async function DELETE(req: Request, ctx: { params: Promise<{ path?: string[] }> }) { return handle('DELETE', req, ctx); }\r\n`,\r\n\r\n 'src/app/api/auth/[...nextauth]/route.ts': `import NextAuth from 'next-auth';\r\nimport { getNextAuthOptions } from '@infuro/cms-core/auth';\r\nimport { getDataSourceInitialized } from '@/lib/data-source';\r\nimport { CMS_ENTITY_MAP } from '@infuro/cms-core';\r\nimport bcrypt from 'bcryptjs';\r\n\r\nasync function getOptions() {\r\n const dataSource = await getDataSourceInitialized();\r\n const userRepo = dataSource.getRepository(CMS_ENTITY_MAP.users);\r\n return getNextAuthOptions({\r\n getUserByEmail: async (email: string) => {\r\n return userRepo.findOne({\r\n where: { email },\r\n relations: ['group', 'group.permissions'],\r\n select: ['id', 'email', 'name', 'password', 'blocked', 'deleted', 'groupId', 'adminAccess'],\r\n }) as any;\r\n },\r\n comparePassword: (plain, hash) => Promise.resolve(bcrypt.compareSync(plain, hash)),\r\n signInPage: '/admin/signin',\r\n });\r\n}\r\n\r\nlet handler: ReturnType<typeof NextAuth> | null = null;\r\n\r\nasync function getHandler() {\r\n if (!handler) handler = NextAuth(await getOptions());\r\n return handler;\r\n}\r\n\r\ntype NextAuthContext = { params: Promise<{ nextauth?: string[] }> };\r\n\r\nexport async function GET(req: Request, context: NextAuthContext) {\r\n return (await getHandler())(req, context);\r\n}\r\nexport async function POST(req: Request, context: NextAuthContext) {\r\n return (await getHandler())(req, context);\r\n}\r\n`,\r\n\r\n 'src/app/admin/layout.tsx': `'use client';\r\n\r\nimport '@infuro/cms-core/admin.css';\r\nimport AdminLayout from '@infuro/cms-core/admin';\r\n\r\nexport default function AdminLayoutWrapper({ children }: { children: React.ReactNode }) {\r\n return (\r\n <AdminLayout\r\n customNavItems={[]}\r\n customNavSections={[]}\r\n customCrudConfigs={{}}\r\n >\r\n {children}\r\n </AdminLayout>\r\n );\r\n}\r\n`,\r\n\r\n 'src/app/admin/[[...slug]]/page.tsx': `import { AdminPageResolver } from '@infuro/cms-core/admin';\r\n\r\nexport default async function AdminPage({ params }: { params: Promise<{ slug?: string[] }> }) {\r\n const { slug } = await params;\r\n return <AdminPageResolver slug={slug} />;\r\n}\r\n`,\r\n\r\n 'src/middleware.ts': `import { NextResponse } from 'next/server';\r\nimport type { NextRequest } from 'next/server';\r\nimport { createCmsMiddleware } from '@infuro/cms-core/auth';\r\n\r\nconst cmsMiddleware = createCmsMiddleware({\r\n publicApiMethods: {\r\n '/api/contacts': ['POST'],\r\n '/api/form-submissions': ['POST'],\r\n '/api/blogs': ['GET'],\r\n '/api/forms': ['GET'],\r\n '/api/auth': ['GET', 'POST'],\r\n '/api/users/forgot-password': ['POST'],\r\n '/api/users/set-password': ['POST'],\r\n '/api/users/invite': ['POST'],\r\n },\r\n});\r\n\r\nexport function middleware(request: NextRequest) {\r\n const result = cmsMiddleware({\r\n nextUrl: request.nextUrl,\r\n url: request.url,\r\n method: request.method,\r\n cookies: request.cookies,\r\n });\r\n\r\n if (result.type === 'next') return NextResponse.next();\r\n if (result.type === 'redirect') return NextResponse.redirect(result.url);\r\n if (result.type === 'json') return NextResponse.json(result.body, { status: result.status });\r\n return NextResponse.next();\r\n}\r\n\r\nexport const config = {\r\n matcher: ['/admin/:path*', '/api/:path*'],\r\n};\r\n`,\r\n\r\n 'src/app/providers.tsx': `\"use client\";\r\n\r\nimport { ThemeProvider } from \"next-themes\";\r\nimport { SessionProvider } from \"next-auth/react\";\r\nimport { Toaster } from \"sonner\";\r\n\r\nexport function Providers({ children }: { children: React.ReactNode }) {\r\n return (\r\n <SessionProvider>\r\n <ThemeProvider attribute=\"class\" defaultTheme=\"system\" enableSystem>\r\n {children}\r\n <Toaster position=\"top-right\" />\r\n </ThemeProvider>\r\n </SessionProvider>\r\n );\r\n}\r\n`,\r\n\r\n '.env.example': `DATABASE_URL=postgres://user:password@localhost:5432/mydb\r\nNEXTAUTH_SECRET=your-random-secret\r\nNEXTAUTH_URL=http://localhost:3000\r\n\r\n# Admin user (for npm run seed)\r\nADMIN_EMAIL=admin@example.com\r\nADMIN_PASSWORD=changeme\r\n`,\r\n\r\n 'src/lib/seed.ts': `try { require('dotenv/config'); } catch {}\r\nimport 'reflect-metadata';\r\nimport { getDataSourceInitialized } from './data-source';\r\nimport { CMS_ENTITY_MAP } from '@infuro/cms-core';\r\nimport bcrypt from 'bcryptjs';\r\n\r\nasync function main() {\r\n const ds = await getDataSourceInitialized();\r\n const userRepo = ds.getRepository(CMS_ENTITY_MAP.users);\r\n\r\n const email = process.env.ADMIN_EMAIL || 'admin@example.com';\r\n const password = process.env.ADMIN_PASSWORD || 'changeme';\r\n\r\n const existing = await userRepo.findOne({ where: { email } });\r\n if (!existing) {\r\n const hashedPassword = await bcrypt.hash(password, 10);\r\n await userRepo.save(userRepo.create({ name: 'Admin', email, password: hashedPassword }));\r\n console.log('Default admin user created');\r\n } else {\r\n console.log('Default admin user already exists');\r\n }\r\n\r\n await ds.destroy();\r\n}\r\n\r\nmain().catch((e) => {\r\n console.error(e);\r\n process.exit(1);\r\n});\r\n`,\r\n\r\n 'scripts/migration-datasource.cjs': `/**\r\n * Data source for TypeORM CLI (migration:generate). Resolves @infuro/cms-core from project root (works with npm link).\r\n */\r\nrequire('reflect-metadata');\r\nrequire('dotenv/config');\r\nconst path = require('path');\r\nconst { DataSource } = require('typeorm');\r\nconst coreEntry = path.resolve(__dirname, '..', 'node_modules', '@infuro', 'cms-core', 'dist', 'index.cjs');\r\nconst { CMS_ENTITY_MAP } = require(coreEntry);\r\n\r\nmodule.exports = new DataSource({\r\n type: 'postgres',\r\n url: process.env.DATABASE_URL,\r\n entities: Object.values(CMS_ENTITY_MAP),\r\n synchronize: false,\r\n migrations: ['src/migrations/*.ts'],\r\n});\r\n`,\r\n\r\n 'scripts/run-migrations.ts': `/**\r\n * Run TypeORM migrations. Loads .env so DATABASE_URL is set.\r\n * Usage: npm run migration:run\r\n */\r\ntry { require('dotenv/config'); } catch {}\r\nimport 'reflect-metadata';\r\n\r\nasync function main() {\r\n process.env.TYPEORM_CLI = '1';\r\n const { getDataSourceInitialized } = await import('../src/lib/data-source');\r\n const ds = await getDataSourceInitialized();\r\n const run = await ds.runMigrations();\r\n console.log(run.length ? \\`Ran \\${run.length} migration(s).\\` : 'No pending migrations.');\r\n await ds.destroy();\r\n}\r\n\r\nmain().catch((e) => {\r\n console.error(e);\r\n process.exit(1);\r\n});\r\n`,\r\n\r\n 'src/migrations/README.md': `# TypeORM migrations\r\n\r\nGenerate a new migration (after changing entities):\r\n\r\n\\`\\`\\`bash\r\nnpm run migration:generate -- MyMigrationName\r\n\\`\\`\\`\r\n\r\nRun pending migrations:\r\n\r\n\\`\\`\\`bash\r\nnpm run migration:run\r\n\\`\\`\\`\r\n`,\r\n\r\n 'src/themes/default/index.ts': `import { createTheme } from '@infuro/cms-core/theme';\r\n\r\nimport { Container, meta as containerMeta } from './components/Container';\r\nimport { TextBlock, meta as textBlockMeta } from './components/TextBlock';\r\n\r\nimport { Navbar } from './layout/Navbar';\r\nimport { Footer, footerFields, footerDefaults } from './layout/Footer';\r\n\r\nexport default createTheme({\r\n name: 'default',\r\n label: 'Default Theme',\r\n components: [\r\n { component: Container, meta: containerMeta },\r\n { component: TextBlock, meta: textBlockMeta },\r\n ],\r\n layout: {\r\n navbar: { component: Navbar },\r\n footer: { component: Footer, fields: footerFields, defaults: footerDefaults },\r\n },\r\n});\r\n`,\r\n\r\n 'src/themes/default/layout/Navbar.tsx': `import type { NavbarConfig, NavItem } from '@infuro/cms-core/theme';\r\n\r\nfunction NavLink({ item }: { item: NavItem }) {\r\n return (\r\n <li className=\"list-none\">\r\n <a\r\n href={item.url}\r\n target={item.openInNewTab ? '_blank' : undefined}\r\n rel={item.openInNewTab ? 'noopener noreferrer' : undefined}\r\n className=\"text-sm font-medium text-gray-700 hover:text-gray-900 px-3 py-2 inline-block\"\r\n >\r\n {item.label}\r\n </a>\r\n </li>\r\n );\r\n}\r\n\r\nexport function Navbar({ logo, items, ctaLabel, ctaUrl }: NavbarConfig) {\r\n return (\r\n <nav className=\"bg-white sticky top-0 z-40 border-b\">\r\n <div className=\"max-w-7xl mx-auto px-4 sm:px-6 lg:px-8\">\r\n <div className=\"flex items-center justify-between h-16\">\r\n <a href=\"/\" className=\"flex-shrink-0\">\r\n {logo ? (\r\n <img src={logo} alt=\"Logo\" className=\"h-8\" />\r\n ) : (\r\n <span className=\"text-xl font-bold text-gray-900\">Logo</span>\r\n )}\r\n </a>\r\n <ul className=\"flex items-center gap-1 list-none m-0 p-0\">\r\n {items.map((item) => (\r\n <NavLink key={item.id} item={item} />\r\n ))}\r\n </ul>\r\n {ctaLabel && (\r\n <a href={ctaUrl || '#'} className=\"text-sm font-medium text-gray-900 hover:underline\">\r\n {ctaLabel}\r\n </a>\r\n )}\r\n </div>\r\n </div>\r\n </nav>\r\n );\r\n}\r\n`,\r\n\r\n 'src/themes/default/layout/Footer.tsx': `import type { FooterConfig, PropDefinition } from '@infuro/cms-core/theme';\r\n\r\nexport const footerFields: PropDefinition[] = [\r\n { name: 'copyright', label: 'Copyright Text', type: 'text' },\r\n];\r\n\r\nexport const footerDefaults: Record<string, any> = {\r\n copyright: '© 2025 Your Site. All rights reserved.',\r\n columns: [],\r\n socialLinks: [],\r\n};\r\n\r\nexport function Footer({ copyright, columns = [], socialLinks = [] }: FooterConfig) {\r\n return (\r\n <footer className=\"bg-gray-900 text-gray-400 py-8 px-8\">\r\n <div className=\"max-w-7xl mx-auto\">\r\n {columns.length > 0 && (\r\n <div className=\"grid grid-cols-2 md:grid-cols-4 gap-8 mb-6\">\r\n {columns.map((col, i) => (\r\n <div key={i}>\r\n <h4 className=\"text-white font-semibold mb-3 text-sm\">{col.title}</h4>\r\n <ul className=\"space-y-2\">\r\n {col.links.map((link, j) => (\r\n <li key={j}>\r\n <a href={link.url} className=\"text-sm hover:text-white transition-colors\">\r\n {link.label}\r\n </a>\r\n </li>\r\n ))}\r\n </ul>\r\n </div>\r\n ))}\r\n </div>\r\n )}\r\n <div className=\"border-t border-gray-800 pt-6\">\r\n <p className=\"text-sm\">{copyright}</p>\r\n </div>\r\n </div>\r\n </footer>\r\n );\r\n}\r\n`,\r\n\r\n 'src/themes/default/components/Container.tsx': `import type { ComponentMeta } from '@infuro/cms-core/theme';\r\n\r\nexport const meta: ComponentMeta = {\r\n name: 'Container',\r\n label: 'Container',\r\n category: 'layout',\r\n icon: 'LayoutDashboard',\r\n description: 'A layout container that holds other components',\r\n defaultProps: { background: '#ffffff' },\r\n props: [{ name: 'background', label: 'Background', type: 'color' }],\r\n canContainChildren: true,\r\n};\r\n\r\nexport function Container({\r\n background = '#ffffff',\r\n children,\r\n}: {\r\n background?: string;\r\n children?: React.ReactNode;\r\n}) {\r\n return (\r\n <div style={{ backgroundColor: background, minHeight: '48px', padding: '1rem' }}>\r\n {children}\r\n </div>\r\n );\r\n}\r\n`,\r\n\r\n 'src/themes/default/components/TextBlock.tsx': `import type { ComponentMeta } from '@infuro/cms-core/theme';\r\n\r\nexport const meta: ComponentMeta = {\r\n name: 'TextBlock',\r\n label: 'Text Block',\r\n category: 'content',\r\n icon: 'Type',\r\n description: 'Rich text content block',\r\n defaultProps: { content: '<p>Enter your text here...</p>' },\r\n props: [{ name: 'content', label: 'Content', type: 'richtext' }],\r\n};\r\n\r\nexport function TextBlock({ content }: { content?: string }) {\r\n return (\r\n <div\r\n className=\"prose prose-gray max-w-none py-4 px-2\"\r\n dangerouslySetInnerHTML={{ __html: content || '' }}\r\n />\r\n );\r\n}\r\n`,\r\n\r\n 'src/lib/theme-registry.ts': `import defaultTheme from '@/themes/default';\r\nimport type { ThemeConfig } from '@infuro/cms-core/theme';\r\n\r\nexport const defaultThemeConfig = defaultTheme;\r\n\r\nexport interface ThemeRegistryItem {\r\n id: string;\r\n label: string;\r\n config: ThemeConfig;\r\n description?: string;\r\n}\r\n\r\nexport const THEME_REGISTRY: ThemeRegistryItem[] = [\r\n {\r\n id: 'default',\r\n label: 'Default',\r\n config: defaultTheme,\r\n description: 'Default theme with standard layout and components.',\r\n },\r\n];\r\n\r\nexport function getThemeById(id: string | undefined): ThemeConfig {\r\n const theme = THEME_REGISTRY.find((t) => t.id === (id || '')) ?? THEME_REGISTRY[0];\r\n return theme?.config ?? defaultTheme;\r\n}\r\n`,\r\n\r\n 'src/app/page.tsx': `export default function HomePage() {\r\n return (\r\n <main className=\"min-h-screen flex flex-col items-center justify-center p-8\">\r\n <h1 className=\"text-3xl font-bold text-gray-900 mb-4\">Welcome</h1>\r\n <p className=\"text-gray-600 mb-6\">Your CMS is set up. Manage content at the admin panel.</p>\r\n <a\r\n href=\"/admin\"\r\n className=\"text-white bg-gray-900 hover:bg-gray-800 px-4 py-2 rounded-lg font-medium transition-colors\"\r\n >\r\n Open Admin\r\n </a>\r\n </main>\r\n );\r\n}\r\n`,\r\n\r\n 'src/app/contact/page.tsx': `export default function ContactPage() {\r\n return (\r\n <main className=\"min-h-screen p-8 max-w-2xl mx-auto\">\r\n <h1 className=\"text-3xl font-bold text-gray-900 mb-4\">Contact</h1>\r\n <p className=\"text-gray-600\">\r\n Add a contact form or wire this page to your CMS form. Use the admin panel to manage forms and submissions.\r\n </p>\r\n </main>\r\n );\r\n}\r\n`,\r\n};\r\n\r\nfunction findRoot(cwd: string): string | null {\r\n let dir = path.resolve(cwd);\r\n for (let i = 0; i < 20; i++) {\r\n const pkgPath = path.join(dir, 'package.json');\r\n if (fs.existsSync(pkgPath)) {\r\n try {\r\n const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));\r\n if (pkg.dependencies?.next || pkg.devDependencies?.next) return dir;\r\n } catch {\r\n // ignore\r\n }\r\n }\r\n const parent = path.dirname(dir);\r\n if (parent === dir) break;\r\n dir = parent;\r\n }\r\n return null;\r\n}\r\n\r\nfunction writeFile(\r\n root: string,\r\n filePath: string,\r\n content: string,\r\n force: boolean,\r\n dryRun: boolean,\r\n log: (msg: string) => void\r\n): boolean {\r\n const full = path.join(root, filePath);\r\n if (fs.existsSync(full) && !force) {\r\n log(` skip (exists): ${filePath}`);\r\n return false;\r\n }\r\n if (dryRun) {\r\n log(` would create: ${filePath}`);\r\n return true;\r\n }\r\n const dir = path.dirname(full);\r\n fs.mkdirSync(dir, { recursive: true });\r\n fs.writeFileSync(full, content, 'utf8');\r\n log(` created: ${filePath}`);\r\n return true;\r\n}\r\n\r\nfunction patchNextConfig(root: string, dryRun: boolean, log: (msg: string) => void): boolean {\r\n const candidates = ['next.config.js', 'next.config.mjs', 'next.config.cjs'];\r\n let configPath: string | null = null;\r\n for (const name of candidates) {\r\n const p = path.join(root, name);\r\n if (fs.existsSync(p)) {\r\n configPath = p;\r\n break;\r\n }\r\n }\r\n if (!configPath) {\r\n log(' skip next.config: not found');\r\n return false;\r\n }\r\n let content = fs.readFileSync(configPath, 'utf8');\r\n if (content.includes(\"'@infuro/cms-core'\") || content.includes('\"@infuro/cms-core\"')) {\r\n log(` skip (already has core): ${path.basename(configPath)}`);\r\n return false;\r\n }\r\n if (content.includes('serverExternalPackages')) {\r\n content = content.replace(\r\n /(serverExternalPackages:\\s*\\[)/,\r\n \"$1'@infuro/cms-core', 'typeorm', \"\r\n );\r\n } else {\r\n content = content.replace(\r\n /(const nextConfig\\s*=\\s*\\{|module\\.exports\\s*=\\s*\\{)/,\r\n \"$1\\n serverExternalPackages: ['@infuro/cms-core', 'typeorm'],\"\r\n );\r\n }\r\n if (dryRun) {\r\n log(` would patch: ${path.basename(configPath)}`);\r\n return true;\r\n }\r\n fs.writeFileSync(configPath, content, 'utf8');\r\n log(` patched: ${path.basename(configPath)}`);\r\n return true;\r\n}\r\n\r\nfunction patchTailwind(root: string, dryRun: boolean, log: (msg: string) => void): boolean {\r\n const candidates = ['tailwind.config.js', 'tailwind.config.mjs', 'tailwind.config.ts'];\r\n let configPath: string | null = null;\r\n for (const name of candidates) {\r\n const p = path.join(root, name);\r\n if (fs.existsSync(p)) {\r\n configPath = p;\r\n break;\r\n }\r\n }\r\n if (!configPath) {\r\n log(' skip tailwind: config not found');\r\n return false;\r\n }\r\n let content = fs.readFileSync(configPath, 'utf8');\r\n const coreContent = \"./node_modules/@infuro/cms-core/dist/**/*.{js,cjs}\";\r\n if (content.includes('@infuro/cms-core')) {\r\n log(` skip (already has core): ${path.basename(configPath)}`);\r\n return false;\r\n }\r\n if (content.includes('content:')) {\r\n content = content.replace(\r\n /(content:\\s*\\[)/,\r\n `$1\\n \"${coreContent}\",`\r\n );\r\n }\r\n if (dryRun) {\r\n log(` would patch: ${path.basename(configPath)}`);\r\n return true;\r\n }\r\n fs.writeFileSync(configPath, content, 'utf8');\r\n log(` patched: ${path.basename(configPath)}`);\r\n return true;\r\n}\r\n\r\nfunction patchLayout(root: string, dryRun: boolean, log: (msg: string) => void): boolean {\r\n const layoutPath = path.join(root, 'src/app/layout.tsx');\r\n if (!fs.existsSync(layoutPath)) {\r\n log(' skip layout: src/app/layout.tsx not found');\r\n return false;\r\n }\r\n let content = fs.readFileSync(layoutPath, 'utf8');\r\n if (content.includes('<Providers>')) {\r\n log(' skip layout: already uses Providers');\r\n return false;\r\n }\r\n const bodyMatch = content.match(/<body([^>]*)>\\s*(\\{children\\})\\s*<\\/body>/s);\r\n if (!bodyMatch) {\r\n log(' skip layout: unexpected structure (add <Providers> manually)');\r\n return false;\r\n }\r\n if (dryRun) {\r\n log(' would patch: src/app/layout.tsx');\r\n return true;\r\n }\r\n const [, bodyAttrs, children] = bodyMatch;\r\n const newBody = `<body${bodyAttrs}>\\n <Providers>${children}</Providers>\\n </body>`;\r\n content = content.replace(/<body[^>]*>\\s*\\{children\\}\\s*<\\/body>/s, newBody);\r\n if (!content.includes(\"from './providers'\") && !content.includes('from \"./providers\"')) {\r\n const firstImport = content.match(/^import .+ from .+;\\n/m);\r\n content = firstImport\r\n ? content.replace(firstImport[0], firstImport[0] + \"import { Providers } from './providers';\\n\")\r\n : \"import { Providers } from './providers';\\n\" + content;\r\n }\r\n if (content.includes('<html') && !content.includes('suppressHydrationWarning')) {\r\n content = content.replace(/<html(\\s)/, '<html suppressHydrationWarning$1');\r\n }\r\n fs.writeFileSync(layoutPath, content, 'utf8');\r\n log(' patched: src/app/layout.tsx');\r\n return true;\r\n}\r\n\r\nfunction patchPackageJson(root: string, dryRun: boolean, log: (msg: string) => void): boolean {\r\n const pkgPath = path.join(root, 'package.json');\r\n if (!fs.existsSync(pkgPath)) return false;\r\n const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));\r\n const scripts = pkg.scripts || {};\r\n let changed = false;\r\n if (!scripts.seed) {\r\n scripts.seed = 'tsx src/lib/seed.ts';\r\n changed = true;\r\n }\r\n if (!scripts['migration:run']) {\r\n scripts['migration:run'] = 'tsx scripts/run-migrations.ts';\r\n changed = true;\r\n }\r\n const dev = pkg.devDependencies || {};\r\n const deps = pkg.dependencies || {};\r\n if (!deps['@infuro/cms-core']) {\r\n deps['@infuro/cms-core'] = '^1.0.6';\r\n changed = true;\r\n }\r\n if (!dev.tsx) {\r\n dev.tsx = '^4.0.0';\r\n changed = true;\r\n }\r\n if (!dev.dotenv) {\r\n dev.dotenv = '^16.0.0';\r\n changed = true;\r\n }\r\n if (!changed) {\r\n log(' skip package.json: scripts/devDeps already present');\r\n return false;\r\n }\r\n pkg.scripts = scripts;\r\n pkg.dependencies = { ...pkg.dependencies, ...deps };\r\n pkg.devDependencies = { ...pkg.devDependencies, ...dev };\r\n if (dryRun) {\r\n log(' would patch: package.json (scripts + devDependencies)');\r\n return true;\r\n }\r\n fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2), 'utf8');\r\n log(' patched: package.json');\r\n return true;\r\n}\r\n\r\nasync function runNpmInstall(root: string, log: (msg: string) => void): Promise<void> {\r\n const { spawnSync } = await import('child_process');\r\n log(' running: npm install (deps)...');\r\n spawnSync('npm', ['install', '@infuro/cms-core', 'typeorm', 'reflect-metadata', 'bcryptjs', 'next-auth', 'next-themes', 'sonner'], {\r\n cwd: root,\r\n stdio: 'inherit',\r\n shell: true,\r\n });\r\n log(' running: npm install -D tsx dotenv @types/node...');\r\n spawnSync('npm', ['install', '-D', 'tsx', 'dotenv', '@types/node'], {\r\n cwd: root,\r\n stdio: 'inherit',\r\n shell: true,\r\n });\r\n}\r\n\r\nasync function runInit(opts: {\r\n force: boolean;\r\n dryRun: boolean;\r\n noDeps: boolean;\r\n noPatchConfig: boolean;\r\n}) {\r\n const log = (msg: string) => console.log(msg);\r\n const cwd = process.cwd();\r\n const root = findRoot(cwd);\r\n if (!root) {\r\n console.error('Not a Next.js project (no package.json with next dependency found from ' + cwd + ')');\r\n process.exit(1);\r\n }\r\n const appDir = path.join(root, 'src/app');\r\n if (!fs.existsSync(appDir)) {\r\n console.error('Expected src/app directory not found. Use a Next.js app with src directory (e.g. create-next-app --src-dir).');\r\n process.exit(1);\r\n }\r\n\r\n log('Infuro CMS init @ ' + root);\r\n if (opts.dryRun) log('(dry run)');\r\n\r\n for (const [filePath, content] of Object.entries(TEMPLATES)) {\r\n writeFile(root, filePath, content, opts.force, opts.dryRun, log);\r\n }\r\n\r\n if (!opts.noPatchConfig) {\r\n log('Config patches:');\r\n patchNextConfig(root, opts.dryRun, log);\r\n patchTailwind(root, opts.dryRun, log);\r\n patchLayout(root, opts.dryRun, log);\r\n patchPackageJson(root, opts.dryRun, log);\r\n }\r\n\r\n if (!opts.noDeps && !opts.dryRun) {\r\n log('Dependencies:');\r\n await runNpmInstall(root, log);\r\n } else if (!opts.noDeps && opts.dryRun) {\r\n log(' would run: npm install @infuro/cms-core typeorm reflect-metadata bcryptjs next-auth next-themes sonner');\r\n log(' would run: npm install -D tsx dotenv @types/node');\r\n }\r\n\r\n log('');\r\n log('Done. Next steps:');\r\n log(' 1. Copy .env.example to .env and set DATABASE_URL, NEXTAUTH_SECRET, NEXTAUTH_URL, ADMIN_EMAIL, ADMIN_PASSWORD');\r\n log(' 2. Run npm run migration:run then npm run seed (creates admin from ADMIN_EMAIL/ADMIN_PASSWORD)');\r\n log(' 3. npm run dev');\r\n}\r\n\r\nconst args = process.argv.slice(2);\r\nconst force = args.includes('--force');\r\nconst dryRun = args.includes('--dry-run');\r\nconst noDeps = args.includes('--no-deps');\r\nconst noPatchConfig = args.includes('--no-patch-config');\r\n\r\nif (args[0] === 'init' || args.includes('--init') || (args.length === 0 && !args.some((a) => a.startsWith('--')))) {\r\n runInit({ force, dryRun, noDeps, noPatchConfig }).catch((e) => {\r\n console.error(e);\r\n process.exit(1);\r\n });\r\n} else {\r\n console.log('Usage: npx @infuro/cms-core init [--force] [--dry-run] [--no-deps] [--no-patch-config]');\r\n process.exit(args[0] === '--help' || args[0] === '-h' ? 0 : 1);\r\n}\r\n"],"mappings":"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","names":["path","fs","force","dryRun"]}
1
+ {"version":3,"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\r\n/**\r\n * @infuro/cms-core init CLI\r\n * Usage: npx @infuro/cms-core init [--force] [--dry-run] [--no-deps] [--no-patch-config]\r\n */\r\nimport fs from 'fs';\r\nimport path from 'path';\r\n\r\nconst TEMPLATES = {\r\n 'src/lib/data-source.ts': `import 'reflect-metadata';\r\nimport path from 'path';\r\nimport { createRequire } from 'module';\r\nimport { DataSource } from 'typeorm';\r\nimport { CMS_ENTITY_MAP } from '@infuro/cms-core';\r\n\r\nconst require = createRequire(import.meta.url);\r\nconst coreDir = path.dirname(require.resolve('@infuro/cms-core'));\r\n\r\nlet dataSource: DataSource | null = null;\r\n\r\nexport function getDataSource(): DataSource {\r\n if (!dataSource) {\r\n dataSource = new DataSource({\r\n type: 'postgres',\r\n url: process.env.DATABASE_URL,\r\n entities: Object.values(CMS_ENTITY_MAP),\r\n synchronize: false,\r\n ...(process.env.TYPEORM_CLI && {\r\n migrations: [\r\n path.join(coreDir, 'migrations', '*.ts'),\r\n path.join(process.cwd(), 'src', 'migrations', '*.ts'),\r\n ],\r\n }),\r\n });\r\n }\r\n return dataSource;\r\n}\r\n\r\nexport async function getDataSourceInitialized(): Promise<DataSource> {\r\n const ds = getDataSource();\r\n if (!ds.isInitialized) await ds.initialize();\r\n return ds;\r\n}\r\n\r\nexport default getDataSource;\r\n`,\r\n\r\n 'src/lib/auth-helpers.ts': `import { getServerSession } from 'next-auth';\r\nimport { NextResponse } from 'next/server';\r\nimport { createAuthHelpers } from '@infuro/cms-core/auth';\r\n\r\nconst helpers = createAuthHelpers(\r\n async () => {\r\n const s = await getServerSession();\r\n return s ? { user: s.user } : null;\r\n },\r\n NextResponse\r\n);\r\n\r\nexport const requireAuth = helpers.requireAuth;\r\nexport const requirePermission = helpers.requirePermission;\r\nexport const requireEntityPermission = helpers.requireEntityPermission;\r\nexport const requireAdminAccess = helpers.requireAdminAccess;\r\nexport const getAuthenticatedUser = helpers.getAuthenticatedUser;\r\n`,\r\n\r\n 'src/lib/cms.ts': `import {\r\n createCmsApp,\r\n localStoragePlugin,\r\n blogGeneratorPlugin,\r\n socialMediaPlugin,\r\n type CmsApp,\r\n} from '@infuro/cms-core';\r\nimport { getDataSourceInitialized } from './data-source';\r\n\r\nlet cmsPromise: Promise<CmsApp> | null = null;\r\n\r\nexport async function getCms(): Promise<CmsApp> {\r\n if (cmsPromise) return cmsPromise;\r\n const dataSource = await getDataSourceInitialized();\r\n cmsPromise = createCmsApp({\r\n dataSource,\r\n config: process.env as unknown as Record<string, string>,\r\n plugins: [\r\n localStoragePlugin({ dir: 'public/uploads' }),\r\n blogGeneratorPlugin(),\r\n socialMediaPlugin(),\r\n ],\r\n });\r\n return cmsPromise;\r\n}\r\n`,\r\n\r\n 'src/app/api/[[...path]]/route.ts': `import { NextResponse } from 'next/server';\r\nimport { getServerSession } from 'next-auth';\r\nimport { createCmsApiHandler } from '@infuro/cms-core/api';\r\nimport { CMS_ENTITY_MAP } from '@infuro/cms-core';\r\nimport { getDataSourceInitialized } from '@/lib/data-source';\r\nimport {\r\n requireAuth,\r\n requireAdminAccess,\r\n requireEntityPermission,\r\n getAuthenticatedUser,\r\n} from '@/lib/auth-helpers';\r\nimport { getCms } from '@/lib/cms';\r\nimport bcrypt from 'bcryptjs';\r\n\r\nconst baseUrl = process.env.NEXTAUTH_URL || 'http://localhost:3000';\r\n\r\nasync function requireAdminApiAuth(req: Request) {\r\n const a = await requireAuth(req);\r\n if (a) return a;\r\n return requireAdminAccess(req);\r\n}\r\n\r\nlet handlerPromise: Promise<ReturnType<typeof createCmsApiHandler>> | null = null;\r\n\r\nasync function getHandler() {\r\n if (!handlerPromise) {\r\n const dataSource = await getDataSourceInitialized();\r\n handlerPromise = Promise.resolve(\r\n createCmsApiHandler({\r\n dataSource,\r\n entityMap: CMS_ENTITY_MAP,\r\n requireAuth: requireAdminApiAuth,\r\n requireEntityPermission,\r\n getSessionUser: getAuthenticatedUser,\r\n json: NextResponse.json.bind(NextResponse),\r\n getCms,\r\n userAuth: {\r\n dataSource,\r\n entityMap: CMS_ENTITY_MAP,\r\n json: NextResponse.json.bind(NextResponse),\r\n baseUrl,\r\n hashPassword: (p) => Promise.resolve(bcrypt.hashSync(p, 12)),\r\n comparePassword: (p, h) => Promise.resolve(bcrypt.compareSync(p, h)),\r\n resetExpiryHours: 1,\r\n getSession: () =>\r\n getServerSession().then((s) => (s ? { user: s.user } : null)),\r\n },\r\n dashboard: {\r\n dataSource,\r\n entityMap: CMS_ENTITY_MAP,\r\n json: NextResponse.json.bind(NextResponse),\r\n requireAuth: requireAdminApiAuth,\r\n requirePermission: requireAdminApiAuth,\r\n },\r\n upload: {\r\n json: NextResponse.json.bind(NextResponse),\r\n requireAuth: requireAdminApiAuth,\r\n storage: () => getCms().then((cms) => cms.getPlugin('storage')),\r\n localUploadDir: 'public/uploads',\r\n },\r\n blogBySlug: {\r\n dataSource,\r\n entityMap: CMS_ENTITY_MAP,\r\n json: NextResponse.json.bind(NextResponse),\r\n requireAuth: async () => null,\r\n },\r\n formBySlug: {\r\n dataSource,\r\n entityMap: CMS_ENTITY_MAP,\r\n json: NextResponse.json.bind(NextResponse),\r\n requireAuth: async () => null,\r\n },\r\n usersApi: {\r\n dataSource,\r\n entityMap: CMS_ENTITY_MAP,\r\n json: NextResponse.json.bind(NextResponse),\r\n requireAuth: requireAdminApiAuth,\r\n baseUrl,\r\n },\r\n userProfile: {\r\n dataSource,\r\n entityMap: CMS_ENTITY_MAP,\r\n json: NextResponse.json.bind(NextResponse),\r\n getSession: () =>\r\n getServerSession().then((s) => (s ? { user: s.user } : null)),\r\n },\r\n })\r\n );\r\n }\r\n return handlerPromise;\r\n}\r\n\r\nasync function handle(method: string, req: Request, context: { params: Promise<{ path?: string[] }> }) {\r\n try {\r\n const handler = await getHandler();\r\n const { path: pathSegments = [] } = await context.params;\r\n return handler.handle(method, pathSegments, req);\r\n } catch {\r\n return NextResponse.json({ error: 'Server Error' }, { status: 500 });\r\n }\r\n}\r\n\r\nexport async function GET(req: Request, ctx: { params: Promise<{ path?: string[] }> }) { return handle('GET', req, ctx); }\r\nexport async function POST(req: Request, ctx: { params: Promise<{ path?: string[] }> }) { return handle('POST', req, ctx); }\r\nexport async function PUT(req: Request, ctx: { params: Promise<{ path?: string[] }> }) { return handle('PUT', req, ctx); }\r\nexport async function PATCH(req: Request, ctx: { params: Promise<{ path?: string[] }> }) { return handle('PATCH', req, ctx); }\r\nexport async function DELETE(req: Request, ctx: { params: Promise<{ path?: string[] }> }) { return handle('DELETE', req, ctx); }\r\n`,\r\n\r\n 'src/app/api/auth/[...nextauth]/route.ts': `import NextAuth from 'next-auth';\r\nimport { getNextAuthOptions } from '@infuro/cms-core/auth';\r\nimport { getDataSourceInitialized } from '@/lib/data-source';\r\nimport { CMS_ENTITY_MAP } from '@infuro/cms-core';\r\nimport bcrypt from 'bcryptjs';\r\n\r\nasync function getOptions() {\r\n const dataSource = await getDataSourceInitialized();\r\n const userRepo = dataSource.getRepository(CMS_ENTITY_MAP.users);\r\n return getNextAuthOptions({\r\n getUserByEmail: async (email: string) => {\r\n return userRepo.findOne({\r\n where: { email },\r\n relations: ['group', 'group.permissions'],\r\n select: ['id', 'email', 'name', 'password', 'blocked', 'deleted', 'groupId', 'adminAccess'],\r\n }) as any;\r\n },\r\n comparePassword: (plain, hash) => Promise.resolve(bcrypt.compareSync(plain, hash)),\r\n signInPage: '/admin/signin',\r\n });\r\n}\r\n\r\nlet handler: ReturnType<typeof NextAuth> | null = null;\r\n\r\nasync function getHandler() {\r\n if (!handler) handler = NextAuth(await getOptions());\r\n return handler;\r\n}\r\n\r\ntype NextAuthContext = { params: Promise<{ nextauth?: string[] }> };\r\n\r\nexport async function GET(req: Request, context: NextAuthContext) {\r\n return (await getHandler())(req, context);\r\n}\r\nexport async function POST(req: Request, context: NextAuthContext) {\r\n return (await getHandler())(req, context);\r\n}\r\n`,\r\n\r\n 'src/app/admin/layout.tsx': `'use client';\r\n\r\nimport '@infuro/cms-core/admin.css';\r\nimport AdminLayout from '@infuro/cms-core/admin';\r\n\r\nexport default function AdminLayoutWrapper({ children }: { children: React.ReactNode }) {\r\n return (\r\n <AdminLayout\r\n customNavItems={[]}\r\n customNavSections={[]}\r\n customCrudConfigs={{}}\r\n >\r\n {children}\r\n </AdminLayout>\r\n );\r\n}\r\n`,\r\n\r\n 'src/app/admin/[[...slug]]/page.tsx': `import { AdminPageResolver } from '@infuro/cms-core/admin';\r\n\r\nexport default async function AdminPage({ params }: { params: Promise<{ slug?: string[] }> }) {\r\n const { slug } = await params;\r\n return <AdminPageResolver slug={slug} />;\r\n}\r\n`,\r\n\r\n 'src/middleware.ts': `import { NextResponse } from 'next/server';\r\nimport type { NextRequest } from 'next/server';\r\nimport { createCmsMiddleware } from '@infuro/cms-core/auth';\r\n\r\nconst cmsMiddleware = createCmsMiddleware({\r\n publicApiMethods: {\r\n '/api/contacts': ['POST'],\r\n '/api/form-submissions': ['POST'],\r\n '/api/blogs': ['GET'],\r\n '/api/forms': ['GET'],\r\n '/api/auth': ['GET', 'POST'],\r\n '/api/users/forgot-password': ['POST'],\r\n '/api/users/set-password': ['POST'],\r\n '/api/users/invite': ['POST'],\r\n },\r\n});\r\n\r\nexport function middleware(request: NextRequest) {\r\n const result = cmsMiddleware({\r\n nextUrl: request.nextUrl,\r\n url: request.url,\r\n method: request.method,\r\n cookies: request.cookies,\r\n });\r\n\r\n if (result.type === 'next') return NextResponse.next();\r\n if (result.type === 'redirect') return NextResponse.redirect(result.url);\r\n if (result.type === 'json') return NextResponse.json(result.body, { status: result.status });\r\n return NextResponse.next();\r\n}\r\n\r\nexport const config = {\r\n matcher: ['/admin/:path*', '/api/:path*'],\r\n};\r\n`,\r\n\r\n 'src/app/providers.tsx': `\"use client\";\r\n\r\nimport { ThemeProvider } from \"next-themes\";\r\nimport { SessionProvider } from \"next-auth/react\";\r\nimport { Toaster } from \"sonner\";\r\n\r\nexport function Providers({ children }: { children: React.ReactNode }) {\r\n return (\r\n <SessionProvider>\r\n <ThemeProvider attribute=\"class\" defaultTheme=\"system\" enableSystem>\r\n {children}\r\n <Toaster position=\"top-right\" />\r\n </ThemeProvider>\r\n </SessionProvider>\r\n );\r\n}\r\n`,\r\n\r\n '.env.example': `DATABASE_URL=postgres://user:password@localhost:5432/mydb\r\nNEXTAUTH_SECRET=your-random-secret\r\nNEXTAUTH_URL=http://localhost:3000\r\n\r\n# Admin user (for npm run seed)\r\nADMIN_EMAIL=admin@example.com\r\nADMIN_PASSWORD=changeme\r\n`,\r\n\r\n 'src/lib/seed.ts': `try { require('dotenv/config'); } catch {}\r\nimport 'reflect-metadata';\r\nimport { getDataSourceInitialized } from './data-source';\r\nimport { CMS_ENTITY_MAP } from '@infuro/cms-core';\r\nimport bcrypt from 'bcryptjs';\r\n\r\nasync function main() {\r\n const ds = await getDataSourceInitialized();\r\n const userRepo = ds.getRepository(CMS_ENTITY_MAP.users);\r\n\r\n const email = process.env.ADMIN_EMAIL || 'admin@example.com';\r\n const password = process.env.ADMIN_PASSWORD || 'changeme';\r\n\r\n const existing = await userRepo.findOne({ where: { email } });\r\n if (!existing) {\r\n const hashedPassword = await bcrypt.hash(password, 10);\r\n await userRepo.save(userRepo.create({ name: 'Admin', email, password: hashedPassword }));\r\n console.log('Default admin user created');\r\n } else {\r\n console.log('Default admin user already exists');\r\n }\r\n\r\n await ds.destroy();\r\n}\r\n\r\nmain().catch((e) => {\r\n console.error(e);\r\n process.exit(1);\r\n});\r\n`,\r\n\r\n 'scripts/migration-datasource.cjs': `/**\r\n * Data source for TypeORM CLI (migration:run / migration:revert).\r\n * Loads app migrations plus core migrations from node_modules/@infuro/cms-core/dist/migrations.\r\n */\r\nrequire('reflect-metadata');\r\nrequire('dotenv/config');\r\nconst fs = require('fs');\r\nconst path = require('path');\r\nconst { DataSource } = require('typeorm');\r\n\r\nconst root = path.resolve(__dirname, '..');\r\nconst coreEntry = path.join(root, 'node_modules', '@infuro', 'cms-core', 'dist', 'index.cjs');\r\nconst { CMS_ENTITY_MAP } = require(coreEntry);\r\n\r\nfunction migrationGlobs() {\r\n const globs = [path.join(root, 'src', 'migrations', '*.ts')];\r\n const coreDist = path.join(root, 'node_modules', '@infuro', 'cms-core', 'dist', 'migrations');\r\n if (fs.existsSync(coreDist)) {\r\n globs.push(path.join(coreDist, '*.js'));\r\n globs.push(path.join(coreDist, '*.ts'));\r\n }\r\n return globs;\r\n}\r\n\r\nmodule.exports = new DataSource({\r\n type: 'postgres',\r\n url: process.env.DATABASE_URL,\r\n entities: Object.values(CMS_ENTITY_MAP),\r\n synchronize: false,\r\n migrations: migrationGlobs(),\r\n});\r\n`,\r\n\r\n 'scripts/run-migrations.ts': `/**\r\n * Run TypeORM migrations. Loads .env so DATABASE_URL is set.\r\n * Usage: npm run migration:run\r\n */\r\ntry { require('dotenv/config'); } catch {}\r\nimport 'reflect-metadata';\r\n\r\nasync function main() {\r\n process.env.TYPEORM_CLI = '1';\r\n const { getDataSourceInitialized } = await import('../src/lib/data-source');\r\n const ds = await getDataSourceInitialized();\r\n const run = await ds.runMigrations();\r\n console.log(run.length ? \\`Ran \\${run.length} migration(s).\\` : 'No pending migrations.');\r\n await ds.destroy();\r\n}\r\n\r\nmain().catch((e) => {\r\n console.error(e);\r\n process.exit(1);\r\n});\r\n`,\r\n\r\n 'src/migrations/README.md': `# TypeORM migrations\r\n\r\nGenerate a new migration (after changing entities):\r\n\r\n\\`\\`\\`bash\r\nnpm run migration:generate -- MyMigrationName\r\n\\`\\`\\`\r\n\r\nRun pending migrations:\r\n\r\n\\`\\`\\`bash\r\nnpm run migration:run\r\n\\`\\`\\`\r\n`,\r\n\r\n 'src/themes/default/index.ts': `import { createTheme } from '@infuro/cms-core/theme';\r\n\r\nimport { Container, meta as containerMeta } from './components/Container';\r\nimport { TextBlock, meta as textBlockMeta } from './components/TextBlock';\r\n\r\nimport { Navbar } from './layout/Navbar';\r\nimport { Footer, footerFields, footerDefaults } from './layout/Footer';\r\n\r\nexport default createTheme({\r\n name: 'default',\r\n label: 'Default Theme',\r\n components: [\r\n { component: Container, meta: containerMeta },\r\n { component: TextBlock, meta: textBlockMeta },\r\n ],\r\n layout: {\r\n navbar: { component: Navbar },\r\n footer: { component: Footer, fields: footerFields, defaults: footerDefaults },\r\n },\r\n});\r\n`,\r\n\r\n 'src/themes/default/layout/Navbar.tsx': `import type { NavbarConfig, NavItem } from '@infuro/cms-core/theme';\r\n\r\nfunction NavLink({ item }: { item: NavItem }) {\r\n return (\r\n <li className=\"list-none\">\r\n <a\r\n href={item.url}\r\n target={item.openInNewTab ? '_blank' : undefined}\r\n rel={item.openInNewTab ? 'noopener noreferrer' : undefined}\r\n className=\"text-sm font-medium text-gray-700 hover:text-gray-900 px-3 py-2 inline-block\"\r\n >\r\n {item.label}\r\n </a>\r\n </li>\r\n );\r\n}\r\n\r\nexport function Navbar({ logo, items, ctaLabel, ctaUrl }: NavbarConfig) {\r\n return (\r\n <nav className=\"bg-white sticky top-0 z-40 border-b\">\r\n <div className=\"max-w-7xl mx-auto px-4 sm:px-6 lg:px-8\">\r\n <div className=\"flex items-center justify-between h-16\">\r\n <a href=\"/\" className=\"flex-shrink-0\">\r\n {logo ? (\r\n <img src={logo} alt=\"Logo\" className=\"h-8\" />\r\n ) : (\r\n <span className=\"text-xl font-bold text-gray-900\">Logo</span>\r\n )}\r\n </a>\r\n <ul className=\"flex items-center gap-1 list-none m-0 p-0\">\r\n {items.map((item) => (\r\n <NavLink key={item.id} item={item} />\r\n ))}\r\n </ul>\r\n {ctaLabel && (\r\n <a href={ctaUrl || '#'} className=\"text-sm font-medium text-gray-900 hover:underline\">\r\n {ctaLabel}\r\n </a>\r\n )}\r\n </div>\r\n </div>\r\n </nav>\r\n );\r\n}\r\n`,\r\n\r\n 'src/themes/default/layout/Footer.tsx': `import type { FooterConfig, PropDefinition } from '@infuro/cms-core/theme';\r\n\r\nexport const footerFields: PropDefinition[] = [\r\n { name: 'copyright', label: 'Copyright Text', type: 'text' },\r\n];\r\n\r\nexport const footerDefaults: Record<string, any> = {\r\n copyright: '© 2025 Your Site. All rights reserved.',\r\n columns: [],\r\n socialLinks: [],\r\n};\r\n\r\nexport function Footer({ copyright, columns = [], socialLinks = [] }: FooterConfig) {\r\n return (\r\n <footer className=\"bg-gray-900 text-gray-400 py-8 px-8\">\r\n <div className=\"max-w-7xl mx-auto\">\r\n {columns.length > 0 && (\r\n <div className=\"grid grid-cols-2 md:grid-cols-4 gap-8 mb-6\">\r\n {columns.map((col, i) => (\r\n <div key={i}>\r\n <h4 className=\"text-white font-semibold mb-3 text-sm\">{col.title}</h4>\r\n <ul className=\"space-y-2\">\r\n {col.links.map((link, j) => (\r\n <li key={j}>\r\n <a href={link.url} className=\"text-sm hover:text-white transition-colors\">\r\n {link.label}\r\n </a>\r\n </li>\r\n ))}\r\n </ul>\r\n </div>\r\n ))}\r\n </div>\r\n )}\r\n <div className=\"border-t border-gray-800 pt-6\">\r\n <p className=\"text-sm\">{copyright}</p>\r\n </div>\r\n </div>\r\n </footer>\r\n );\r\n}\r\n`,\r\n\r\n 'src/themes/default/components/Container.tsx': `import type { ComponentMeta } from '@infuro/cms-core/theme';\r\n\r\nexport const meta: ComponentMeta = {\r\n name: 'Container',\r\n label: 'Container',\r\n category: 'layout',\r\n icon: 'LayoutDashboard',\r\n description: 'A layout container that holds other components',\r\n defaultProps: { background: '#ffffff' },\r\n props: [{ name: 'background', label: 'Background', type: 'color' }],\r\n canContainChildren: true,\r\n};\r\n\r\nexport function Container({\r\n background = '#ffffff',\r\n children,\r\n}: {\r\n background?: string;\r\n children?: React.ReactNode;\r\n}) {\r\n return (\r\n <div style={{ backgroundColor: background, minHeight: '48px', padding: '1rem' }}>\r\n {children}\r\n </div>\r\n );\r\n}\r\n`,\r\n\r\n 'src/themes/default/components/TextBlock.tsx': `import type { ComponentMeta } from '@infuro/cms-core/theme';\r\n\r\nexport const meta: ComponentMeta = {\r\n name: 'TextBlock',\r\n label: 'Text Block',\r\n category: 'content',\r\n icon: 'Type',\r\n description: 'Rich text content block',\r\n defaultProps: { content: '<p>Enter your text here...</p>' },\r\n props: [{ name: 'content', label: 'Content', type: 'richtext' }],\r\n};\r\n\r\nexport function TextBlock({ content }: { content?: string }) {\r\n return (\r\n <div\r\n className=\"prose prose-gray max-w-none py-4 px-2\"\r\n dangerouslySetInnerHTML={{ __html: content || '' }}\r\n />\r\n );\r\n}\r\n`,\r\n\r\n 'src/lib/theme-registry.ts': `import defaultTheme from '@/themes/default';\r\nimport type { ThemeConfig } from '@infuro/cms-core/theme';\r\n\r\nexport const defaultThemeConfig = defaultTheme;\r\n\r\nexport interface ThemeRegistryItem {\r\n id: string;\r\n label: string;\r\n config: ThemeConfig;\r\n description?: string;\r\n}\r\n\r\nexport const THEME_REGISTRY: ThemeRegistryItem[] = [\r\n {\r\n id: 'default',\r\n label: 'Default',\r\n config: defaultTheme,\r\n description: 'Default theme with standard layout and components.',\r\n },\r\n];\r\n\r\nexport function getThemeById(id: string | undefined): ThemeConfig {\r\n const theme = THEME_REGISTRY.find((t) => t.id === (id || '')) ?? THEME_REGISTRY[0];\r\n return theme?.config ?? defaultTheme;\r\n}\r\n`,\r\n\r\n 'src/app/page.tsx': `export default function HomePage() {\r\n return (\r\n <main className=\"min-h-screen flex flex-col items-center justify-center p-8\">\r\n <h1 className=\"text-3xl font-bold text-gray-900 mb-4\">Welcome</h1>\r\n <p className=\"text-gray-600 mb-6\">Your CMS is set up. Manage content at the admin panel.</p>\r\n <a\r\n href=\"/admin\"\r\n className=\"text-white bg-gray-900 hover:bg-gray-800 px-4 py-2 rounded-lg font-medium transition-colors\"\r\n >\r\n Open Admin\r\n </a>\r\n </main>\r\n );\r\n}\r\n`,\r\n\r\n 'src/app/contact/page.tsx': `export default function ContactPage() {\r\n return (\r\n <main className=\"min-h-screen p-8 max-w-2xl mx-auto\">\r\n <h1 className=\"text-3xl font-bold text-gray-900 mb-4\">Contact</h1>\r\n <p className=\"text-gray-600\">\r\n Add a contact form or wire this page to your CMS form. Use the admin panel to manage forms and submissions.\r\n </p>\r\n </main>\r\n );\r\n}\r\n`,\r\n};\r\n\r\nfunction findRoot(cwd: string): string | null {\r\n let dir = path.resolve(cwd);\r\n for (let i = 0; i < 20; i++) {\r\n const pkgPath = path.join(dir, 'package.json');\r\n if (fs.existsSync(pkgPath)) {\r\n try {\r\n const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));\r\n if (pkg.dependencies?.next || pkg.devDependencies?.next) return dir;\r\n } catch {\r\n // ignore\r\n }\r\n }\r\n const parent = path.dirname(dir);\r\n if (parent === dir) break;\r\n dir = parent;\r\n }\r\n return null;\r\n}\r\n\r\nfunction writeFile(\r\n root: string,\r\n filePath: string,\r\n content: string,\r\n force: boolean,\r\n dryRun: boolean,\r\n log: (msg: string) => void\r\n): boolean {\r\n const full = path.join(root, filePath);\r\n if (fs.existsSync(full) && !force) {\r\n log(` skip (exists): ${filePath}`);\r\n return false;\r\n }\r\n if (dryRun) {\r\n log(` would create: ${filePath}`);\r\n return true;\r\n }\r\n const dir = path.dirname(full);\r\n fs.mkdirSync(dir, { recursive: true });\r\n fs.writeFileSync(full, content, 'utf8');\r\n log(` created: ${filePath}`);\r\n return true;\r\n}\r\n\r\nfunction patchNextConfig(root: string, dryRun: boolean, log: (msg: string) => void): boolean {\r\n const candidates = ['next.config.js', 'next.config.mjs', 'next.config.cjs'];\r\n let configPath: string | null = null;\r\n for (const name of candidates) {\r\n const p = path.join(root, name);\r\n if (fs.existsSync(p)) {\r\n configPath = p;\r\n break;\r\n }\r\n }\r\n if (!configPath) {\r\n log(' skip next.config: not found');\r\n return false;\r\n }\r\n let content = fs.readFileSync(configPath, 'utf8');\r\n if (content.includes(\"'@infuro/cms-core'\") || content.includes('\"@infuro/cms-core\"')) {\r\n log(` skip (already has core): ${path.basename(configPath)}`);\r\n return false;\r\n }\r\n if (content.includes('serverExternalPackages')) {\r\n content = content.replace(\r\n /(serverExternalPackages:\\s*\\[)/,\r\n \"$1'@infuro/cms-core', 'typeorm', 'rss-parser', \"\r\n );\r\n } else {\r\n content = content.replace(\r\n /(const nextConfig\\s*=\\s*\\{|module\\.exports\\s*=\\s*\\{)/,\r\n \"$1\\n serverExternalPackages: ['@infuro/cms-core', 'typeorm', 'rss-parser'],\"\r\n );\r\n }\r\n if (dryRun) {\r\n log(` would patch: ${path.basename(configPath)}`);\r\n return true;\r\n }\r\n fs.writeFileSync(configPath, content, 'utf8');\r\n log(` patched: ${path.basename(configPath)}`);\r\n return true;\r\n}\r\n\r\nfunction patchTailwind(root: string, dryRun: boolean, log: (msg: string) => void): boolean {\r\n const candidates = ['tailwind.config.js', 'tailwind.config.mjs', 'tailwind.config.ts'];\r\n let configPath: string | null = null;\r\n for (const name of candidates) {\r\n const p = path.join(root, name);\r\n if (fs.existsSync(p)) {\r\n configPath = p;\r\n break;\r\n }\r\n }\r\n if (!configPath) {\r\n log(' skip tailwind: config not found');\r\n return false;\r\n }\r\n let content = fs.readFileSync(configPath, 'utf8');\r\n const coreContent = \"./node_modules/@infuro/cms-core/dist/**/*.{js,cjs}\";\r\n if (content.includes('@infuro/cms-core')) {\r\n log(` skip (already has core): ${path.basename(configPath)}`);\r\n return false;\r\n }\r\n if (content.includes('content:')) {\r\n content = content.replace(\r\n /(content:\\s*\\[)/,\r\n `$1\\n \"${coreContent}\",`\r\n );\r\n }\r\n if (dryRun) {\r\n log(` would patch: ${path.basename(configPath)}`);\r\n return true;\r\n }\r\n fs.writeFileSync(configPath, content, 'utf8');\r\n log(` patched: ${path.basename(configPath)}`);\r\n return true;\r\n}\r\n\r\nfunction patchLayout(root: string, dryRun: boolean, log: (msg: string) => void): boolean {\r\n const layoutPath = path.join(root, 'src/app/layout.tsx');\r\n if (!fs.existsSync(layoutPath)) {\r\n log(' skip layout: src/app/layout.tsx not found');\r\n return false;\r\n }\r\n let content = fs.readFileSync(layoutPath, 'utf8');\r\n if (content.includes('<Providers>')) {\r\n log(' skip layout: already uses Providers');\r\n return false;\r\n }\r\n const bodyMatch = content.match(/<body([^>]*)>\\s*(\\{children\\})\\s*<\\/body>/s);\r\n if (!bodyMatch) {\r\n log(' skip layout: unexpected structure (add <Providers> manually)');\r\n return false;\r\n }\r\n if (dryRun) {\r\n log(' would patch: src/app/layout.tsx');\r\n return true;\r\n }\r\n const [, bodyAttrs, children] = bodyMatch;\r\n const newBody = `<body${bodyAttrs}>\\n <Providers>${children}</Providers>\\n </body>`;\r\n content = content.replace(/<body[^>]*>\\s*\\{children\\}\\s*<\\/body>/s, newBody);\r\n if (!content.includes(\"from './providers'\") && !content.includes('from \"./providers\"')) {\r\n const firstImport = content.match(/^import .+ from .+;\\n/m);\r\n content = firstImport\r\n ? content.replace(firstImport[0], firstImport[0] + \"import { Providers } from './providers';\\n\")\r\n : \"import { Providers } from './providers';\\n\" + content;\r\n }\r\n if (content.includes('<html') && !content.includes('suppressHydrationWarning')) {\r\n content = content.replace(/<html(\\s)/, '<html suppressHydrationWarning$1');\r\n }\r\n fs.writeFileSync(layoutPath, content, 'utf8');\r\n log(' patched: src/app/layout.tsx');\r\n return true;\r\n}\r\n\r\nfunction patchPackageJson(root: string, dryRun: boolean, log: (msg: string) => void): boolean {\r\n const pkgPath = path.join(root, 'package.json');\r\n if (!fs.existsSync(pkgPath)) return false;\r\n const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));\r\n const scripts = pkg.scripts || {};\r\n let changed = false;\r\n if (!scripts.seed) {\r\n scripts.seed = 'tsx src/lib/seed.ts';\r\n changed = true;\r\n }\r\n if (!scripts['migration:run']) {\r\n scripts['migration:run'] = 'tsx scripts/run-migrations.ts';\r\n changed = true;\r\n }\r\n const dev = pkg.devDependencies || {};\r\n const deps = pkg.dependencies || {};\r\n if (!deps['@infuro/cms-core']) {\r\n deps['@infuro/cms-core'] = '^1.0.6';\r\n changed = true;\r\n }\r\n if (!dev.tsx) {\r\n dev.tsx = '^4.0.0';\r\n changed = true;\r\n }\r\n if (!dev.dotenv) {\r\n dev.dotenv = '^16.0.0';\r\n changed = true;\r\n }\r\n if (!changed) {\r\n log(' skip package.json: scripts/devDeps already present');\r\n return false;\r\n }\r\n pkg.scripts = scripts;\r\n pkg.dependencies = { ...pkg.dependencies, ...deps };\r\n pkg.devDependencies = { ...pkg.devDependencies, ...dev };\r\n if (dryRun) {\r\n log(' would patch: package.json (scripts + devDependencies)');\r\n return true;\r\n }\r\n fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2), 'utf8');\r\n log(' patched: package.json');\r\n return true;\r\n}\r\n\r\nasync function runNpmInstall(root: string, log: (msg: string) => void): Promise<void> {\r\n const { spawnSync } = await import('child_process');\r\n log(' running: npm install (deps)...');\r\n spawnSync('npm', ['install', '@infuro/cms-core', 'typeorm', 'reflect-metadata', 'bcryptjs', 'next-auth', 'next-themes', 'sonner'], {\r\n cwd: root,\r\n stdio: 'inherit',\r\n shell: true,\r\n });\r\n log(' running: npm install -D tsx dotenv @types/node...');\r\n spawnSync('npm', ['install', '-D', 'tsx', 'dotenv', '@types/node'], {\r\n cwd: root,\r\n stdio: 'inherit',\r\n shell: true,\r\n });\r\n}\r\n\r\nasync function runInit(opts: {\r\n force: boolean;\r\n dryRun: boolean;\r\n noDeps: boolean;\r\n noPatchConfig: boolean;\r\n}) {\r\n const log = (msg: string) => console.log(msg);\r\n const cwd = process.cwd();\r\n const root = findRoot(cwd);\r\n if (!root) {\r\n console.error('Not a Next.js project (no package.json with next dependency found from ' + cwd + ')');\r\n process.exit(1);\r\n }\r\n const appDir = path.join(root, 'src/app');\r\n if (!fs.existsSync(appDir)) {\r\n console.error('Expected src/app directory not found. Use a Next.js app with src directory (e.g. create-next-app --src-dir).');\r\n process.exit(1);\r\n }\r\n\r\n log('Infuro CMS init @ ' + root);\r\n if (opts.dryRun) log('(dry run)');\r\n\r\n for (const [filePath, content] of Object.entries(TEMPLATES)) {\r\n writeFile(root, filePath, content, opts.force, opts.dryRun, log);\r\n }\r\n\r\n if (!opts.noPatchConfig) {\r\n log('Config patches:');\r\n patchNextConfig(root, opts.dryRun, log);\r\n patchTailwind(root, opts.dryRun, log);\r\n patchLayout(root, opts.dryRun, log);\r\n patchPackageJson(root, opts.dryRun, log);\r\n }\r\n\r\n if (!opts.noDeps && !opts.dryRun) {\r\n log('Dependencies:');\r\n await runNpmInstall(root, log);\r\n } else if (!opts.noDeps && opts.dryRun) {\r\n log(' would run: npm install @infuro/cms-core typeorm reflect-metadata bcryptjs next-auth next-themes sonner');\r\n log(' would run: npm install -D tsx dotenv @types/node');\r\n }\r\n\r\n log('');\r\n log('Done. Next steps:');\r\n log(' 1. Copy .env.example to .env and set DATABASE_URL, NEXTAUTH_SECRET, NEXTAUTH_URL, ADMIN_EMAIL, ADMIN_PASSWORD');\r\n log(' 2. Run npm run migration:run then npm run seed (creates admin from ADMIN_EMAIL/ADMIN_PASSWORD)');\r\n log(' 3. npm run dev');\r\n}\r\n\r\nconst args = process.argv.slice(2);\r\nconst force = args.includes('--force');\r\nconst dryRun = args.includes('--dry-run');\r\nconst noDeps = args.includes('--no-deps');\r\nconst noPatchConfig = args.includes('--no-patch-config');\r\n\r\nif (args[0] === 'init' || args.includes('--init') || (args.length === 0 && !args.some((a) => a.startsWith('--')))) {\r\n runInit({ force, dryRun, noDeps, noPatchConfig }).catch((e) => {\r\n console.error(e);\r\n process.exit(1);\r\n });\r\n} else {\r\n console.log('Usage: npx @infuro/cms-core init [--force] [--dry-run] [--no-deps] [--no-patch-config]');\r\n process.exit(args[0] === '--help' || args[0] === '-h' ? 0 : 1);\r\n}\r\n"],"mappings":"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","names":["path","fs","force","dryRun"]}
package/dist/cli.js CHANGED
@@ -62,6 +62,8 @@ export const getAuthenticatedUser = helpers.getAuthenticatedUser;
62
62
  "src/lib/cms.ts": `import {
63
63
  createCmsApp,
64
64
  localStoragePlugin,
65
+ blogGeneratorPlugin,
66
+ socialMediaPlugin,
65
67
  type CmsApp,
66
68
  } from '@infuro/cms-core';
67
69
  import { getDataSourceInitialized } from './data-source';
@@ -76,6 +78,8 @@ export async function getCms(): Promise<CmsApp> {
76
78
  config: process.env as unknown as Record<string, string>,
77
79
  plugins: [
78
80
  localStoragePlugin({ dir: 'public/uploads' }),
81
+ blogGeneratorPlugin(),
82
+ socialMediaPlugin(),
79
83
  ],
80
84
  });
81
85
  return cmsPromise;
@@ -342,21 +346,35 @@ main().catch((e) => {
342
346
  });
343
347
  `,
344
348
  "scripts/migration-datasource.cjs": `/**
345
- * Data source for TypeORM CLI (migration:generate). Resolves @infuro/cms-core from project root (works with npm link).
349
+ * Data source for TypeORM CLI (migration:run / migration:revert).
350
+ * Loads app migrations plus core migrations from node_modules/@infuro/cms-core/dist/migrations.
346
351
  */
347
352
  require('reflect-metadata');
348
353
  require('dotenv/config');
354
+ const fs = require('fs');
349
355
  const path = require('path');
350
356
  const { DataSource } = require('typeorm');
351
- const coreEntry = path.resolve(__dirname, '..', 'node_modules', '@infuro', 'cms-core', 'dist', 'index.cjs');
357
+
358
+ const root = path.resolve(__dirname, '..');
359
+ const coreEntry = path.join(root, 'node_modules', '@infuro', 'cms-core', 'dist', 'index.cjs');
352
360
  const { CMS_ENTITY_MAP } = require(coreEntry);
353
361
 
362
+ function migrationGlobs() {
363
+ const globs = [path.join(root, 'src', 'migrations', '*.ts')];
364
+ const coreDist = path.join(root, 'node_modules', '@infuro', 'cms-core', 'dist', 'migrations');
365
+ if (fs.existsSync(coreDist)) {
366
+ globs.push(path.join(coreDist, '*.js'));
367
+ globs.push(path.join(coreDist, '*.ts'));
368
+ }
369
+ return globs;
370
+ }
371
+
354
372
  module.exports = new DataSource({
355
373
  type: 'postgres',
356
374
  url: process.env.DATABASE_URL,
357
375
  entities: Object.values(CMS_ENTITY_MAP),
358
376
  synchronize: false,
359
- migrations: ['src/migrations/*.ts'],
377
+ migrations: migrationGlobs(),
360
378
  });
361
379
  `,
362
380
  "scripts/run-migrations.ts": `/**
@@ -658,12 +676,12 @@ function patchNextConfig(root, dryRun2, log) {
658
676
  if (content.includes("serverExternalPackages")) {
659
677
  content = content.replace(
660
678
  /(serverExternalPackages:\s*\[)/,
661
- "$1'@infuro/cms-core', 'typeorm', "
679
+ "$1'@infuro/cms-core', 'typeorm', 'rss-parser', "
662
680
  );
663
681
  } else {
664
682
  content = content.replace(
665
683
  /(const nextConfig\s*=\s*\{|module\.exports\s*=\s*\{)/,
666
- "$1\n serverExternalPackages: ['@infuro/cms-core', 'typeorm'],"
684
+ "$1\n serverExternalPackages: ['@infuro/cms-core', 'typeorm', 'rss-parser'],"
667
685
  );
668
686
  }
669
687
  if (dryRun2) {
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\r\n/**\r\n * @infuro/cms-core init CLI\r\n * Usage: npx @infuro/cms-core init [--force] [--dry-run] [--no-deps] [--no-patch-config]\r\n */\r\nimport fs from 'fs';\r\nimport path from 'path';\r\n\r\nconst TEMPLATES = {\r\n 'src/lib/data-source.ts': `import 'reflect-metadata';\r\nimport path from 'path';\r\nimport { createRequire } from 'module';\r\nimport { DataSource } from 'typeorm';\r\nimport { CMS_ENTITY_MAP } from '@infuro/cms-core';\r\n\r\nconst require = createRequire(import.meta.url);\r\nconst coreDir = path.dirname(require.resolve('@infuro/cms-core'));\r\n\r\nlet dataSource: DataSource | null = null;\r\n\r\nexport function getDataSource(): DataSource {\r\n if (!dataSource) {\r\n dataSource = new DataSource({\r\n type: 'postgres',\r\n url: process.env.DATABASE_URL,\r\n entities: Object.values(CMS_ENTITY_MAP),\r\n synchronize: false,\r\n ...(process.env.TYPEORM_CLI && {\r\n migrations: [\r\n path.join(coreDir, 'migrations', '*.ts'),\r\n path.join(process.cwd(), 'src', 'migrations', '*.ts'),\r\n ],\r\n }),\r\n });\r\n }\r\n return dataSource;\r\n}\r\n\r\nexport async function getDataSourceInitialized(): Promise<DataSource> {\r\n const ds = getDataSource();\r\n if (!ds.isInitialized) await ds.initialize();\r\n return ds;\r\n}\r\n\r\nexport default getDataSource;\r\n`,\r\n\r\n 'src/lib/auth-helpers.ts': `import { getServerSession } from 'next-auth';\r\nimport { NextResponse } from 'next/server';\r\nimport { createAuthHelpers } from '@infuro/cms-core/auth';\r\n\r\nconst helpers = createAuthHelpers(\r\n async () => {\r\n const s = await getServerSession();\r\n return s ? { user: s.user } : null;\r\n },\r\n NextResponse\r\n);\r\n\r\nexport const requireAuth = helpers.requireAuth;\r\nexport const requirePermission = helpers.requirePermission;\r\nexport const requireEntityPermission = helpers.requireEntityPermission;\r\nexport const requireAdminAccess = helpers.requireAdminAccess;\r\nexport const getAuthenticatedUser = helpers.getAuthenticatedUser;\r\n`,\r\n\r\n 'src/lib/cms.ts': `import {\r\n createCmsApp,\r\n localStoragePlugin,\r\n type CmsApp,\r\n} from '@infuro/cms-core';\r\nimport { getDataSourceInitialized } from './data-source';\r\n\r\nlet cmsPromise: Promise<CmsApp> | null = null;\r\n\r\nexport async function getCms(): Promise<CmsApp> {\r\n if (cmsPromise) return cmsPromise;\r\n const dataSource = await getDataSourceInitialized();\r\n cmsPromise = createCmsApp({\r\n dataSource,\r\n config: process.env as unknown as Record<string, string>,\r\n plugins: [\r\n localStoragePlugin({ dir: 'public/uploads' }),\r\n ],\r\n });\r\n return cmsPromise;\r\n}\r\n`,\r\n\r\n 'src/app/api/[[...path]]/route.ts': `import { NextResponse } from 'next/server';\r\nimport { getServerSession } from 'next-auth';\r\nimport { createCmsApiHandler } from '@infuro/cms-core/api';\r\nimport { CMS_ENTITY_MAP } from '@infuro/cms-core';\r\nimport { getDataSourceInitialized } from '@/lib/data-source';\r\nimport {\r\n requireAuth,\r\n requireAdminAccess,\r\n requireEntityPermission,\r\n getAuthenticatedUser,\r\n} from '@/lib/auth-helpers';\r\nimport { getCms } from '@/lib/cms';\r\nimport bcrypt from 'bcryptjs';\r\n\r\nconst baseUrl = process.env.NEXTAUTH_URL || 'http://localhost:3000';\r\n\r\nasync function requireAdminApiAuth(req: Request) {\r\n const a = await requireAuth(req);\r\n if (a) return a;\r\n return requireAdminAccess(req);\r\n}\r\n\r\nlet handlerPromise: Promise<ReturnType<typeof createCmsApiHandler>> | null = null;\r\n\r\nasync function getHandler() {\r\n if (!handlerPromise) {\r\n const dataSource = await getDataSourceInitialized();\r\n handlerPromise = Promise.resolve(\r\n createCmsApiHandler({\r\n dataSource,\r\n entityMap: CMS_ENTITY_MAP,\r\n requireAuth: requireAdminApiAuth,\r\n requireEntityPermission,\r\n getSessionUser: getAuthenticatedUser,\r\n json: NextResponse.json.bind(NextResponse),\r\n getCms,\r\n userAuth: {\r\n dataSource,\r\n entityMap: CMS_ENTITY_MAP,\r\n json: NextResponse.json.bind(NextResponse),\r\n baseUrl,\r\n hashPassword: (p) => Promise.resolve(bcrypt.hashSync(p, 12)),\r\n comparePassword: (p, h) => Promise.resolve(bcrypt.compareSync(p, h)),\r\n resetExpiryHours: 1,\r\n getSession: () =>\r\n getServerSession().then((s) => (s ? { user: s.user } : null)),\r\n },\r\n dashboard: {\r\n dataSource,\r\n entityMap: CMS_ENTITY_MAP,\r\n json: NextResponse.json.bind(NextResponse),\r\n requireAuth: requireAdminApiAuth,\r\n requirePermission: requireAdminApiAuth,\r\n },\r\n upload: {\r\n json: NextResponse.json.bind(NextResponse),\r\n requireAuth: requireAdminApiAuth,\r\n storage: () => getCms().then((cms) => cms.getPlugin('storage')),\r\n localUploadDir: 'public/uploads',\r\n },\r\n blogBySlug: {\r\n dataSource,\r\n entityMap: CMS_ENTITY_MAP,\r\n json: NextResponse.json.bind(NextResponse),\r\n requireAuth: async () => null,\r\n },\r\n formBySlug: {\r\n dataSource,\r\n entityMap: CMS_ENTITY_MAP,\r\n json: NextResponse.json.bind(NextResponse),\r\n requireAuth: async () => null,\r\n },\r\n usersApi: {\r\n dataSource,\r\n entityMap: CMS_ENTITY_MAP,\r\n json: NextResponse.json.bind(NextResponse),\r\n requireAuth: requireAdminApiAuth,\r\n baseUrl,\r\n },\r\n userProfile: {\r\n dataSource,\r\n entityMap: CMS_ENTITY_MAP,\r\n json: NextResponse.json.bind(NextResponse),\r\n getSession: () =>\r\n getServerSession().then((s) => (s ? { user: s.user } : null)),\r\n },\r\n })\r\n );\r\n }\r\n return handlerPromise;\r\n}\r\n\r\nasync function handle(method: string, req: Request, context: { params: Promise<{ path?: string[] }> }) {\r\n try {\r\n const handler = await getHandler();\r\n const { path: pathSegments = [] } = await context.params;\r\n return handler.handle(method, pathSegments, req);\r\n } catch {\r\n return NextResponse.json({ error: 'Server Error' }, { status: 500 });\r\n }\r\n}\r\n\r\nexport async function GET(req: Request, ctx: { params: Promise<{ path?: string[] }> }) { return handle('GET', req, ctx); }\r\nexport async function POST(req: Request, ctx: { params: Promise<{ path?: string[] }> }) { return handle('POST', req, ctx); }\r\nexport async function PUT(req: Request, ctx: { params: Promise<{ path?: string[] }> }) { return handle('PUT', req, ctx); }\r\nexport async function PATCH(req: Request, ctx: { params: Promise<{ path?: string[] }> }) { return handle('PATCH', req, ctx); }\r\nexport async function DELETE(req: Request, ctx: { params: Promise<{ path?: string[] }> }) { return handle('DELETE', req, ctx); }\r\n`,\r\n\r\n 'src/app/api/auth/[...nextauth]/route.ts': `import NextAuth from 'next-auth';\r\nimport { getNextAuthOptions } from '@infuro/cms-core/auth';\r\nimport { getDataSourceInitialized } from '@/lib/data-source';\r\nimport { CMS_ENTITY_MAP } from '@infuro/cms-core';\r\nimport bcrypt from 'bcryptjs';\r\n\r\nasync function getOptions() {\r\n const dataSource = await getDataSourceInitialized();\r\n const userRepo = dataSource.getRepository(CMS_ENTITY_MAP.users);\r\n return getNextAuthOptions({\r\n getUserByEmail: async (email: string) => {\r\n return userRepo.findOne({\r\n where: { email },\r\n relations: ['group', 'group.permissions'],\r\n select: ['id', 'email', 'name', 'password', 'blocked', 'deleted', 'groupId', 'adminAccess'],\r\n }) as any;\r\n },\r\n comparePassword: (plain, hash) => Promise.resolve(bcrypt.compareSync(plain, hash)),\r\n signInPage: '/admin/signin',\r\n });\r\n}\r\n\r\nlet handler: ReturnType<typeof NextAuth> | null = null;\r\n\r\nasync function getHandler() {\r\n if (!handler) handler = NextAuth(await getOptions());\r\n return handler;\r\n}\r\n\r\ntype NextAuthContext = { params: Promise<{ nextauth?: string[] }> };\r\n\r\nexport async function GET(req: Request, context: NextAuthContext) {\r\n return (await getHandler())(req, context);\r\n}\r\nexport async function POST(req: Request, context: NextAuthContext) {\r\n return (await getHandler())(req, context);\r\n}\r\n`,\r\n\r\n 'src/app/admin/layout.tsx': `'use client';\r\n\r\nimport '@infuro/cms-core/admin.css';\r\nimport AdminLayout from '@infuro/cms-core/admin';\r\n\r\nexport default function AdminLayoutWrapper({ children }: { children: React.ReactNode }) {\r\n return (\r\n <AdminLayout\r\n customNavItems={[]}\r\n customNavSections={[]}\r\n customCrudConfigs={{}}\r\n >\r\n {children}\r\n </AdminLayout>\r\n );\r\n}\r\n`,\r\n\r\n 'src/app/admin/[[...slug]]/page.tsx': `import { AdminPageResolver } from '@infuro/cms-core/admin';\r\n\r\nexport default async function AdminPage({ params }: { params: Promise<{ slug?: string[] }> }) {\r\n const { slug } = await params;\r\n return <AdminPageResolver slug={slug} />;\r\n}\r\n`,\r\n\r\n 'src/middleware.ts': `import { NextResponse } from 'next/server';\r\nimport type { NextRequest } from 'next/server';\r\nimport { createCmsMiddleware } from '@infuro/cms-core/auth';\r\n\r\nconst cmsMiddleware = createCmsMiddleware({\r\n publicApiMethods: {\r\n '/api/contacts': ['POST'],\r\n '/api/form-submissions': ['POST'],\r\n '/api/blogs': ['GET'],\r\n '/api/forms': ['GET'],\r\n '/api/auth': ['GET', 'POST'],\r\n '/api/users/forgot-password': ['POST'],\r\n '/api/users/set-password': ['POST'],\r\n '/api/users/invite': ['POST'],\r\n },\r\n});\r\n\r\nexport function middleware(request: NextRequest) {\r\n const result = cmsMiddleware({\r\n nextUrl: request.nextUrl,\r\n url: request.url,\r\n method: request.method,\r\n cookies: request.cookies,\r\n });\r\n\r\n if (result.type === 'next') return NextResponse.next();\r\n if (result.type === 'redirect') return NextResponse.redirect(result.url);\r\n if (result.type === 'json') return NextResponse.json(result.body, { status: result.status });\r\n return NextResponse.next();\r\n}\r\n\r\nexport const config = {\r\n matcher: ['/admin/:path*', '/api/:path*'],\r\n};\r\n`,\r\n\r\n 'src/app/providers.tsx': `\"use client\";\r\n\r\nimport { ThemeProvider } from \"next-themes\";\r\nimport { SessionProvider } from \"next-auth/react\";\r\nimport { Toaster } from \"sonner\";\r\n\r\nexport function Providers({ children }: { children: React.ReactNode }) {\r\n return (\r\n <SessionProvider>\r\n <ThemeProvider attribute=\"class\" defaultTheme=\"system\" enableSystem>\r\n {children}\r\n <Toaster position=\"top-right\" />\r\n </ThemeProvider>\r\n </SessionProvider>\r\n );\r\n}\r\n`,\r\n\r\n '.env.example': `DATABASE_URL=postgres://user:password@localhost:5432/mydb\r\nNEXTAUTH_SECRET=your-random-secret\r\nNEXTAUTH_URL=http://localhost:3000\r\n\r\n# Admin user (for npm run seed)\r\nADMIN_EMAIL=admin@example.com\r\nADMIN_PASSWORD=changeme\r\n`,\r\n\r\n 'src/lib/seed.ts': `try { require('dotenv/config'); } catch {}\r\nimport 'reflect-metadata';\r\nimport { getDataSourceInitialized } from './data-source';\r\nimport { CMS_ENTITY_MAP } from '@infuro/cms-core';\r\nimport bcrypt from 'bcryptjs';\r\n\r\nasync function main() {\r\n const ds = await getDataSourceInitialized();\r\n const userRepo = ds.getRepository(CMS_ENTITY_MAP.users);\r\n\r\n const email = process.env.ADMIN_EMAIL || 'admin@example.com';\r\n const password = process.env.ADMIN_PASSWORD || 'changeme';\r\n\r\n const existing = await userRepo.findOne({ where: { email } });\r\n if (!existing) {\r\n const hashedPassword = await bcrypt.hash(password, 10);\r\n await userRepo.save(userRepo.create({ name: 'Admin', email, password: hashedPassword }));\r\n console.log('Default admin user created');\r\n } else {\r\n console.log('Default admin user already exists');\r\n }\r\n\r\n await ds.destroy();\r\n}\r\n\r\nmain().catch((e) => {\r\n console.error(e);\r\n process.exit(1);\r\n});\r\n`,\r\n\r\n 'scripts/migration-datasource.cjs': `/**\r\n * Data source for TypeORM CLI (migration:generate). Resolves @infuro/cms-core from project root (works with npm link).\r\n */\r\nrequire('reflect-metadata');\r\nrequire('dotenv/config');\r\nconst path = require('path');\r\nconst { DataSource } = require('typeorm');\r\nconst coreEntry = path.resolve(__dirname, '..', 'node_modules', '@infuro', 'cms-core', 'dist', 'index.cjs');\r\nconst { CMS_ENTITY_MAP } = require(coreEntry);\r\n\r\nmodule.exports = new DataSource({\r\n type: 'postgres',\r\n url: process.env.DATABASE_URL,\r\n entities: Object.values(CMS_ENTITY_MAP),\r\n synchronize: false,\r\n migrations: ['src/migrations/*.ts'],\r\n});\r\n`,\r\n\r\n 'scripts/run-migrations.ts': `/**\r\n * Run TypeORM migrations. Loads .env so DATABASE_URL is set.\r\n * Usage: npm run migration:run\r\n */\r\ntry { require('dotenv/config'); } catch {}\r\nimport 'reflect-metadata';\r\n\r\nasync function main() {\r\n process.env.TYPEORM_CLI = '1';\r\n const { getDataSourceInitialized } = await import('../src/lib/data-source');\r\n const ds = await getDataSourceInitialized();\r\n const run = await ds.runMigrations();\r\n console.log(run.length ? \\`Ran \\${run.length} migration(s).\\` : 'No pending migrations.');\r\n await ds.destroy();\r\n}\r\n\r\nmain().catch((e) => {\r\n console.error(e);\r\n process.exit(1);\r\n});\r\n`,\r\n\r\n 'src/migrations/README.md': `# TypeORM migrations\r\n\r\nGenerate a new migration (after changing entities):\r\n\r\n\\`\\`\\`bash\r\nnpm run migration:generate -- MyMigrationName\r\n\\`\\`\\`\r\n\r\nRun pending migrations:\r\n\r\n\\`\\`\\`bash\r\nnpm run migration:run\r\n\\`\\`\\`\r\n`,\r\n\r\n 'src/themes/default/index.ts': `import { createTheme } from '@infuro/cms-core/theme';\r\n\r\nimport { Container, meta as containerMeta } from './components/Container';\r\nimport { TextBlock, meta as textBlockMeta } from './components/TextBlock';\r\n\r\nimport { Navbar } from './layout/Navbar';\r\nimport { Footer, footerFields, footerDefaults } from './layout/Footer';\r\n\r\nexport default createTheme({\r\n name: 'default',\r\n label: 'Default Theme',\r\n components: [\r\n { component: Container, meta: containerMeta },\r\n { component: TextBlock, meta: textBlockMeta },\r\n ],\r\n layout: {\r\n navbar: { component: Navbar },\r\n footer: { component: Footer, fields: footerFields, defaults: footerDefaults },\r\n },\r\n});\r\n`,\r\n\r\n 'src/themes/default/layout/Navbar.tsx': `import type { NavbarConfig, NavItem } from '@infuro/cms-core/theme';\r\n\r\nfunction NavLink({ item }: { item: NavItem }) {\r\n return (\r\n <li className=\"list-none\">\r\n <a\r\n href={item.url}\r\n target={item.openInNewTab ? '_blank' : undefined}\r\n rel={item.openInNewTab ? 'noopener noreferrer' : undefined}\r\n className=\"text-sm font-medium text-gray-700 hover:text-gray-900 px-3 py-2 inline-block\"\r\n >\r\n {item.label}\r\n </a>\r\n </li>\r\n );\r\n}\r\n\r\nexport function Navbar({ logo, items, ctaLabel, ctaUrl }: NavbarConfig) {\r\n return (\r\n <nav className=\"bg-white sticky top-0 z-40 border-b\">\r\n <div className=\"max-w-7xl mx-auto px-4 sm:px-6 lg:px-8\">\r\n <div className=\"flex items-center justify-between h-16\">\r\n <a href=\"/\" className=\"flex-shrink-0\">\r\n {logo ? (\r\n <img src={logo} alt=\"Logo\" className=\"h-8\" />\r\n ) : (\r\n <span className=\"text-xl font-bold text-gray-900\">Logo</span>\r\n )}\r\n </a>\r\n <ul className=\"flex items-center gap-1 list-none m-0 p-0\">\r\n {items.map((item) => (\r\n <NavLink key={item.id} item={item} />\r\n ))}\r\n </ul>\r\n {ctaLabel && (\r\n <a href={ctaUrl || '#'} className=\"text-sm font-medium text-gray-900 hover:underline\">\r\n {ctaLabel}\r\n </a>\r\n )}\r\n </div>\r\n </div>\r\n </nav>\r\n );\r\n}\r\n`,\r\n\r\n 'src/themes/default/layout/Footer.tsx': `import type { FooterConfig, PropDefinition } from '@infuro/cms-core/theme';\r\n\r\nexport const footerFields: PropDefinition[] = [\r\n { name: 'copyright', label: 'Copyright Text', type: 'text' },\r\n];\r\n\r\nexport const footerDefaults: Record<string, any> = {\r\n copyright: '© 2025 Your Site. All rights reserved.',\r\n columns: [],\r\n socialLinks: [],\r\n};\r\n\r\nexport function Footer({ copyright, columns = [], socialLinks = [] }: FooterConfig) {\r\n return (\r\n <footer className=\"bg-gray-900 text-gray-400 py-8 px-8\">\r\n <div className=\"max-w-7xl mx-auto\">\r\n {columns.length > 0 && (\r\n <div className=\"grid grid-cols-2 md:grid-cols-4 gap-8 mb-6\">\r\n {columns.map((col, i) => (\r\n <div key={i}>\r\n <h4 className=\"text-white font-semibold mb-3 text-sm\">{col.title}</h4>\r\n <ul className=\"space-y-2\">\r\n {col.links.map((link, j) => (\r\n <li key={j}>\r\n <a href={link.url} className=\"text-sm hover:text-white transition-colors\">\r\n {link.label}\r\n </a>\r\n </li>\r\n ))}\r\n </ul>\r\n </div>\r\n ))}\r\n </div>\r\n )}\r\n <div className=\"border-t border-gray-800 pt-6\">\r\n <p className=\"text-sm\">{copyright}</p>\r\n </div>\r\n </div>\r\n </footer>\r\n );\r\n}\r\n`,\r\n\r\n 'src/themes/default/components/Container.tsx': `import type { ComponentMeta } from '@infuro/cms-core/theme';\r\n\r\nexport const meta: ComponentMeta = {\r\n name: 'Container',\r\n label: 'Container',\r\n category: 'layout',\r\n icon: 'LayoutDashboard',\r\n description: 'A layout container that holds other components',\r\n defaultProps: { background: '#ffffff' },\r\n props: [{ name: 'background', label: 'Background', type: 'color' }],\r\n canContainChildren: true,\r\n};\r\n\r\nexport function Container({\r\n background = '#ffffff',\r\n children,\r\n}: {\r\n background?: string;\r\n children?: React.ReactNode;\r\n}) {\r\n return (\r\n <div style={{ backgroundColor: background, minHeight: '48px', padding: '1rem' }}>\r\n {children}\r\n </div>\r\n );\r\n}\r\n`,\r\n\r\n 'src/themes/default/components/TextBlock.tsx': `import type { ComponentMeta } from '@infuro/cms-core/theme';\r\n\r\nexport const meta: ComponentMeta = {\r\n name: 'TextBlock',\r\n label: 'Text Block',\r\n category: 'content',\r\n icon: 'Type',\r\n description: 'Rich text content block',\r\n defaultProps: { content: '<p>Enter your text here...</p>' },\r\n props: [{ name: 'content', label: 'Content', type: 'richtext' }],\r\n};\r\n\r\nexport function TextBlock({ content }: { content?: string }) {\r\n return (\r\n <div\r\n className=\"prose prose-gray max-w-none py-4 px-2\"\r\n dangerouslySetInnerHTML={{ __html: content || '' }}\r\n />\r\n );\r\n}\r\n`,\r\n\r\n 'src/lib/theme-registry.ts': `import defaultTheme from '@/themes/default';\r\nimport type { ThemeConfig } from '@infuro/cms-core/theme';\r\n\r\nexport const defaultThemeConfig = defaultTheme;\r\n\r\nexport interface ThemeRegistryItem {\r\n id: string;\r\n label: string;\r\n config: ThemeConfig;\r\n description?: string;\r\n}\r\n\r\nexport const THEME_REGISTRY: ThemeRegistryItem[] = [\r\n {\r\n id: 'default',\r\n label: 'Default',\r\n config: defaultTheme,\r\n description: 'Default theme with standard layout and components.',\r\n },\r\n];\r\n\r\nexport function getThemeById(id: string | undefined): ThemeConfig {\r\n const theme = THEME_REGISTRY.find((t) => t.id === (id || '')) ?? THEME_REGISTRY[0];\r\n return theme?.config ?? defaultTheme;\r\n}\r\n`,\r\n\r\n 'src/app/page.tsx': `export default function HomePage() {\r\n return (\r\n <main className=\"min-h-screen flex flex-col items-center justify-center p-8\">\r\n <h1 className=\"text-3xl font-bold text-gray-900 mb-4\">Welcome</h1>\r\n <p className=\"text-gray-600 mb-6\">Your CMS is set up. Manage content at the admin panel.</p>\r\n <a\r\n href=\"/admin\"\r\n className=\"text-white bg-gray-900 hover:bg-gray-800 px-4 py-2 rounded-lg font-medium transition-colors\"\r\n >\r\n Open Admin\r\n </a>\r\n </main>\r\n );\r\n}\r\n`,\r\n\r\n 'src/app/contact/page.tsx': `export default function ContactPage() {\r\n return (\r\n <main className=\"min-h-screen p-8 max-w-2xl mx-auto\">\r\n <h1 className=\"text-3xl font-bold text-gray-900 mb-4\">Contact</h1>\r\n <p className=\"text-gray-600\">\r\n Add a contact form or wire this page to your CMS form. Use the admin panel to manage forms and submissions.\r\n </p>\r\n </main>\r\n );\r\n}\r\n`,\r\n};\r\n\r\nfunction findRoot(cwd: string): string | null {\r\n let dir = path.resolve(cwd);\r\n for (let i = 0; i < 20; i++) {\r\n const pkgPath = path.join(dir, 'package.json');\r\n if (fs.existsSync(pkgPath)) {\r\n try {\r\n const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));\r\n if (pkg.dependencies?.next || pkg.devDependencies?.next) return dir;\r\n } catch {\r\n // ignore\r\n }\r\n }\r\n const parent = path.dirname(dir);\r\n if (parent === dir) break;\r\n dir = parent;\r\n }\r\n return null;\r\n}\r\n\r\nfunction writeFile(\r\n root: string,\r\n filePath: string,\r\n content: string,\r\n force: boolean,\r\n dryRun: boolean,\r\n log: (msg: string) => void\r\n): boolean {\r\n const full = path.join(root, filePath);\r\n if (fs.existsSync(full) && !force) {\r\n log(` skip (exists): ${filePath}`);\r\n return false;\r\n }\r\n if (dryRun) {\r\n log(` would create: ${filePath}`);\r\n return true;\r\n }\r\n const dir = path.dirname(full);\r\n fs.mkdirSync(dir, { recursive: true });\r\n fs.writeFileSync(full, content, 'utf8');\r\n log(` created: ${filePath}`);\r\n return true;\r\n}\r\n\r\nfunction patchNextConfig(root: string, dryRun: boolean, log: (msg: string) => void): boolean {\r\n const candidates = ['next.config.js', 'next.config.mjs', 'next.config.cjs'];\r\n let configPath: string | null = null;\r\n for (const name of candidates) {\r\n const p = path.join(root, name);\r\n if (fs.existsSync(p)) {\r\n configPath = p;\r\n break;\r\n }\r\n }\r\n if (!configPath) {\r\n log(' skip next.config: not found');\r\n return false;\r\n }\r\n let content = fs.readFileSync(configPath, 'utf8');\r\n if (content.includes(\"'@infuro/cms-core'\") || content.includes('\"@infuro/cms-core\"')) {\r\n log(` skip (already has core): ${path.basename(configPath)}`);\r\n return false;\r\n }\r\n if (content.includes('serverExternalPackages')) {\r\n content = content.replace(\r\n /(serverExternalPackages:\\s*\\[)/,\r\n \"$1'@infuro/cms-core', 'typeorm', \"\r\n );\r\n } else {\r\n content = content.replace(\r\n /(const nextConfig\\s*=\\s*\\{|module\\.exports\\s*=\\s*\\{)/,\r\n \"$1\\n serverExternalPackages: ['@infuro/cms-core', 'typeorm'],\"\r\n );\r\n }\r\n if (dryRun) {\r\n log(` would patch: ${path.basename(configPath)}`);\r\n return true;\r\n }\r\n fs.writeFileSync(configPath, content, 'utf8');\r\n log(` patched: ${path.basename(configPath)}`);\r\n return true;\r\n}\r\n\r\nfunction patchTailwind(root: string, dryRun: boolean, log: (msg: string) => void): boolean {\r\n const candidates = ['tailwind.config.js', 'tailwind.config.mjs', 'tailwind.config.ts'];\r\n let configPath: string | null = null;\r\n for (const name of candidates) {\r\n const p = path.join(root, name);\r\n if (fs.existsSync(p)) {\r\n configPath = p;\r\n break;\r\n }\r\n }\r\n if (!configPath) {\r\n log(' skip tailwind: config not found');\r\n return false;\r\n }\r\n let content = fs.readFileSync(configPath, 'utf8');\r\n const coreContent = \"./node_modules/@infuro/cms-core/dist/**/*.{js,cjs}\";\r\n if (content.includes('@infuro/cms-core')) {\r\n log(` skip (already has core): ${path.basename(configPath)}`);\r\n return false;\r\n }\r\n if (content.includes('content:')) {\r\n content = content.replace(\r\n /(content:\\s*\\[)/,\r\n `$1\\n \"${coreContent}\",`\r\n );\r\n }\r\n if (dryRun) {\r\n log(` would patch: ${path.basename(configPath)}`);\r\n return true;\r\n }\r\n fs.writeFileSync(configPath, content, 'utf8');\r\n log(` patched: ${path.basename(configPath)}`);\r\n return true;\r\n}\r\n\r\nfunction patchLayout(root: string, dryRun: boolean, log: (msg: string) => void): boolean {\r\n const layoutPath = path.join(root, 'src/app/layout.tsx');\r\n if (!fs.existsSync(layoutPath)) {\r\n log(' skip layout: src/app/layout.tsx not found');\r\n return false;\r\n }\r\n let content = fs.readFileSync(layoutPath, 'utf8');\r\n if (content.includes('<Providers>')) {\r\n log(' skip layout: already uses Providers');\r\n return false;\r\n }\r\n const bodyMatch = content.match(/<body([^>]*)>\\s*(\\{children\\})\\s*<\\/body>/s);\r\n if (!bodyMatch) {\r\n log(' skip layout: unexpected structure (add <Providers> manually)');\r\n return false;\r\n }\r\n if (dryRun) {\r\n log(' would patch: src/app/layout.tsx');\r\n return true;\r\n }\r\n const [, bodyAttrs, children] = bodyMatch;\r\n const newBody = `<body${bodyAttrs}>\\n <Providers>${children}</Providers>\\n </body>`;\r\n content = content.replace(/<body[^>]*>\\s*\\{children\\}\\s*<\\/body>/s, newBody);\r\n if (!content.includes(\"from './providers'\") && !content.includes('from \"./providers\"')) {\r\n const firstImport = content.match(/^import .+ from .+;\\n/m);\r\n content = firstImport\r\n ? content.replace(firstImport[0], firstImport[0] + \"import { Providers } from './providers';\\n\")\r\n : \"import { Providers } from './providers';\\n\" + content;\r\n }\r\n if (content.includes('<html') && !content.includes('suppressHydrationWarning')) {\r\n content = content.replace(/<html(\\s)/, '<html suppressHydrationWarning$1');\r\n }\r\n fs.writeFileSync(layoutPath, content, 'utf8');\r\n log(' patched: src/app/layout.tsx');\r\n return true;\r\n}\r\n\r\nfunction patchPackageJson(root: string, dryRun: boolean, log: (msg: string) => void): boolean {\r\n const pkgPath = path.join(root, 'package.json');\r\n if (!fs.existsSync(pkgPath)) return false;\r\n const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));\r\n const scripts = pkg.scripts || {};\r\n let changed = false;\r\n if (!scripts.seed) {\r\n scripts.seed = 'tsx src/lib/seed.ts';\r\n changed = true;\r\n }\r\n if (!scripts['migration:run']) {\r\n scripts['migration:run'] = 'tsx scripts/run-migrations.ts';\r\n changed = true;\r\n }\r\n const dev = pkg.devDependencies || {};\r\n const deps = pkg.dependencies || {};\r\n if (!deps['@infuro/cms-core']) {\r\n deps['@infuro/cms-core'] = '^1.0.6';\r\n changed = true;\r\n }\r\n if (!dev.tsx) {\r\n dev.tsx = '^4.0.0';\r\n changed = true;\r\n }\r\n if (!dev.dotenv) {\r\n dev.dotenv = '^16.0.0';\r\n changed = true;\r\n }\r\n if (!changed) {\r\n log(' skip package.json: scripts/devDeps already present');\r\n return false;\r\n }\r\n pkg.scripts = scripts;\r\n pkg.dependencies = { ...pkg.dependencies, ...deps };\r\n pkg.devDependencies = { ...pkg.devDependencies, ...dev };\r\n if (dryRun) {\r\n log(' would patch: package.json (scripts + devDependencies)');\r\n return true;\r\n }\r\n fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2), 'utf8');\r\n log(' patched: package.json');\r\n return true;\r\n}\r\n\r\nasync function runNpmInstall(root: string, log: (msg: string) => void): Promise<void> {\r\n const { spawnSync } = await import('child_process');\r\n log(' running: npm install (deps)...');\r\n spawnSync('npm', ['install', '@infuro/cms-core', 'typeorm', 'reflect-metadata', 'bcryptjs', 'next-auth', 'next-themes', 'sonner'], {\r\n cwd: root,\r\n stdio: 'inherit',\r\n shell: true,\r\n });\r\n log(' running: npm install -D tsx dotenv @types/node...');\r\n spawnSync('npm', ['install', '-D', 'tsx', 'dotenv', '@types/node'], {\r\n cwd: root,\r\n stdio: 'inherit',\r\n shell: true,\r\n });\r\n}\r\n\r\nasync function runInit(opts: {\r\n force: boolean;\r\n dryRun: boolean;\r\n noDeps: boolean;\r\n noPatchConfig: boolean;\r\n}) {\r\n const log = (msg: string) => console.log(msg);\r\n const cwd = process.cwd();\r\n const root = findRoot(cwd);\r\n if (!root) {\r\n console.error('Not a Next.js project (no package.json with next dependency found from ' + cwd + ')');\r\n process.exit(1);\r\n }\r\n const appDir = path.join(root, 'src/app');\r\n if (!fs.existsSync(appDir)) {\r\n console.error('Expected src/app directory not found. Use a Next.js app with src directory (e.g. create-next-app --src-dir).');\r\n process.exit(1);\r\n }\r\n\r\n log('Infuro CMS init @ ' + root);\r\n if (opts.dryRun) log('(dry run)');\r\n\r\n for (const [filePath, content] of Object.entries(TEMPLATES)) {\r\n writeFile(root, filePath, content, opts.force, opts.dryRun, log);\r\n }\r\n\r\n if (!opts.noPatchConfig) {\r\n log('Config patches:');\r\n patchNextConfig(root, opts.dryRun, log);\r\n patchTailwind(root, opts.dryRun, log);\r\n patchLayout(root, opts.dryRun, log);\r\n patchPackageJson(root, opts.dryRun, log);\r\n }\r\n\r\n if (!opts.noDeps && !opts.dryRun) {\r\n log('Dependencies:');\r\n await runNpmInstall(root, log);\r\n } else if (!opts.noDeps && opts.dryRun) {\r\n log(' would run: npm install @infuro/cms-core typeorm reflect-metadata bcryptjs next-auth next-themes sonner');\r\n log(' would run: npm install -D tsx dotenv @types/node');\r\n }\r\n\r\n log('');\r\n log('Done. Next steps:');\r\n log(' 1. Copy .env.example to .env and set DATABASE_URL, NEXTAUTH_SECRET, NEXTAUTH_URL, ADMIN_EMAIL, ADMIN_PASSWORD');\r\n log(' 2. Run npm run migration:run then npm run seed (creates admin from ADMIN_EMAIL/ADMIN_PASSWORD)');\r\n log(' 3. npm run dev');\r\n}\r\n\r\nconst args = process.argv.slice(2);\r\nconst force = args.includes('--force');\r\nconst dryRun = args.includes('--dry-run');\r\nconst noDeps = args.includes('--no-deps');\r\nconst noPatchConfig = args.includes('--no-patch-config');\r\n\r\nif (args[0] === 'init' || args.includes('--init') || (args.length === 0 && !args.some((a) => a.startsWith('--')))) {\r\n runInit({ force, dryRun, noDeps, noPatchConfig }).catch((e) => {\r\n console.error(e);\r\n process.exit(1);\r\n });\r\n} else {\r\n console.log('Usage: npx @infuro/cms-core init [--force] [--dry-run] [--no-deps] [--no-patch-config]');\r\n process.exit(args[0] === '--help' || args[0] === '-h' ? 0 : 1);\r\n}\r\n"],"mappings":"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","names":["force","dryRun"]}
1
+ {"version":3,"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\r\n/**\r\n * @infuro/cms-core init CLI\r\n * Usage: npx @infuro/cms-core init [--force] [--dry-run] [--no-deps] [--no-patch-config]\r\n */\r\nimport fs from 'fs';\r\nimport path from 'path';\r\n\r\nconst TEMPLATES = {\r\n 'src/lib/data-source.ts': `import 'reflect-metadata';\r\nimport path from 'path';\r\nimport { createRequire } from 'module';\r\nimport { DataSource } from 'typeorm';\r\nimport { CMS_ENTITY_MAP } from '@infuro/cms-core';\r\n\r\nconst require = createRequire(import.meta.url);\r\nconst coreDir = path.dirname(require.resolve('@infuro/cms-core'));\r\n\r\nlet dataSource: DataSource | null = null;\r\n\r\nexport function getDataSource(): DataSource {\r\n if (!dataSource) {\r\n dataSource = new DataSource({\r\n type: 'postgres',\r\n url: process.env.DATABASE_URL,\r\n entities: Object.values(CMS_ENTITY_MAP),\r\n synchronize: false,\r\n ...(process.env.TYPEORM_CLI && {\r\n migrations: [\r\n path.join(coreDir, 'migrations', '*.ts'),\r\n path.join(process.cwd(), 'src', 'migrations', '*.ts'),\r\n ],\r\n }),\r\n });\r\n }\r\n return dataSource;\r\n}\r\n\r\nexport async function getDataSourceInitialized(): Promise<DataSource> {\r\n const ds = getDataSource();\r\n if (!ds.isInitialized) await ds.initialize();\r\n return ds;\r\n}\r\n\r\nexport default getDataSource;\r\n`,\r\n\r\n 'src/lib/auth-helpers.ts': `import { getServerSession } from 'next-auth';\r\nimport { NextResponse } from 'next/server';\r\nimport { createAuthHelpers } from '@infuro/cms-core/auth';\r\n\r\nconst helpers = createAuthHelpers(\r\n async () => {\r\n const s = await getServerSession();\r\n return s ? { user: s.user } : null;\r\n },\r\n NextResponse\r\n);\r\n\r\nexport const requireAuth = helpers.requireAuth;\r\nexport const requirePermission = helpers.requirePermission;\r\nexport const requireEntityPermission = helpers.requireEntityPermission;\r\nexport const requireAdminAccess = helpers.requireAdminAccess;\r\nexport const getAuthenticatedUser = helpers.getAuthenticatedUser;\r\n`,\r\n\r\n 'src/lib/cms.ts': `import {\r\n createCmsApp,\r\n localStoragePlugin,\r\n blogGeneratorPlugin,\r\n socialMediaPlugin,\r\n type CmsApp,\r\n} from '@infuro/cms-core';\r\nimport { getDataSourceInitialized } from './data-source';\r\n\r\nlet cmsPromise: Promise<CmsApp> | null = null;\r\n\r\nexport async function getCms(): Promise<CmsApp> {\r\n if (cmsPromise) return cmsPromise;\r\n const dataSource = await getDataSourceInitialized();\r\n cmsPromise = createCmsApp({\r\n dataSource,\r\n config: process.env as unknown as Record<string, string>,\r\n plugins: [\r\n localStoragePlugin({ dir: 'public/uploads' }),\r\n blogGeneratorPlugin(),\r\n socialMediaPlugin(),\r\n ],\r\n });\r\n return cmsPromise;\r\n}\r\n`,\r\n\r\n 'src/app/api/[[...path]]/route.ts': `import { NextResponse } from 'next/server';\r\nimport { getServerSession } from 'next-auth';\r\nimport { createCmsApiHandler } from '@infuro/cms-core/api';\r\nimport { CMS_ENTITY_MAP } from '@infuro/cms-core';\r\nimport { getDataSourceInitialized } from '@/lib/data-source';\r\nimport {\r\n requireAuth,\r\n requireAdminAccess,\r\n requireEntityPermission,\r\n getAuthenticatedUser,\r\n} from '@/lib/auth-helpers';\r\nimport { getCms } from '@/lib/cms';\r\nimport bcrypt from 'bcryptjs';\r\n\r\nconst baseUrl = process.env.NEXTAUTH_URL || 'http://localhost:3000';\r\n\r\nasync function requireAdminApiAuth(req: Request) {\r\n const a = await requireAuth(req);\r\n if (a) return a;\r\n return requireAdminAccess(req);\r\n}\r\n\r\nlet handlerPromise: Promise<ReturnType<typeof createCmsApiHandler>> | null = null;\r\n\r\nasync function getHandler() {\r\n if (!handlerPromise) {\r\n const dataSource = await getDataSourceInitialized();\r\n handlerPromise = Promise.resolve(\r\n createCmsApiHandler({\r\n dataSource,\r\n entityMap: CMS_ENTITY_MAP,\r\n requireAuth: requireAdminApiAuth,\r\n requireEntityPermission,\r\n getSessionUser: getAuthenticatedUser,\r\n json: NextResponse.json.bind(NextResponse),\r\n getCms,\r\n userAuth: {\r\n dataSource,\r\n entityMap: CMS_ENTITY_MAP,\r\n json: NextResponse.json.bind(NextResponse),\r\n baseUrl,\r\n hashPassword: (p) => Promise.resolve(bcrypt.hashSync(p, 12)),\r\n comparePassword: (p, h) => Promise.resolve(bcrypt.compareSync(p, h)),\r\n resetExpiryHours: 1,\r\n getSession: () =>\r\n getServerSession().then((s) => (s ? { user: s.user } : null)),\r\n },\r\n dashboard: {\r\n dataSource,\r\n entityMap: CMS_ENTITY_MAP,\r\n json: NextResponse.json.bind(NextResponse),\r\n requireAuth: requireAdminApiAuth,\r\n requirePermission: requireAdminApiAuth,\r\n },\r\n upload: {\r\n json: NextResponse.json.bind(NextResponse),\r\n requireAuth: requireAdminApiAuth,\r\n storage: () => getCms().then((cms) => cms.getPlugin('storage')),\r\n localUploadDir: 'public/uploads',\r\n },\r\n blogBySlug: {\r\n dataSource,\r\n entityMap: CMS_ENTITY_MAP,\r\n json: NextResponse.json.bind(NextResponse),\r\n requireAuth: async () => null,\r\n },\r\n formBySlug: {\r\n dataSource,\r\n entityMap: CMS_ENTITY_MAP,\r\n json: NextResponse.json.bind(NextResponse),\r\n requireAuth: async () => null,\r\n },\r\n usersApi: {\r\n dataSource,\r\n entityMap: CMS_ENTITY_MAP,\r\n json: NextResponse.json.bind(NextResponse),\r\n requireAuth: requireAdminApiAuth,\r\n baseUrl,\r\n },\r\n userProfile: {\r\n dataSource,\r\n entityMap: CMS_ENTITY_MAP,\r\n json: NextResponse.json.bind(NextResponse),\r\n getSession: () =>\r\n getServerSession().then((s) => (s ? { user: s.user } : null)),\r\n },\r\n })\r\n );\r\n }\r\n return handlerPromise;\r\n}\r\n\r\nasync function handle(method: string, req: Request, context: { params: Promise<{ path?: string[] }> }) {\r\n try {\r\n const handler = await getHandler();\r\n const { path: pathSegments = [] } = await context.params;\r\n return handler.handle(method, pathSegments, req);\r\n } catch {\r\n return NextResponse.json({ error: 'Server Error' }, { status: 500 });\r\n }\r\n}\r\n\r\nexport async function GET(req: Request, ctx: { params: Promise<{ path?: string[] }> }) { return handle('GET', req, ctx); }\r\nexport async function POST(req: Request, ctx: { params: Promise<{ path?: string[] }> }) { return handle('POST', req, ctx); }\r\nexport async function PUT(req: Request, ctx: { params: Promise<{ path?: string[] }> }) { return handle('PUT', req, ctx); }\r\nexport async function PATCH(req: Request, ctx: { params: Promise<{ path?: string[] }> }) { return handle('PATCH', req, ctx); }\r\nexport async function DELETE(req: Request, ctx: { params: Promise<{ path?: string[] }> }) { return handle('DELETE', req, ctx); }\r\n`,\r\n\r\n 'src/app/api/auth/[...nextauth]/route.ts': `import NextAuth from 'next-auth';\r\nimport { getNextAuthOptions } from '@infuro/cms-core/auth';\r\nimport { getDataSourceInitialized } from '@/lib/data-source';\r\nimport { CMS_ENTITY_MAP } from '@infuro/cms-core';\r\nimport bcrypt from 'bcryptjs';\r\n\r\nasync function getOptions() {\r\n const dataSource = await getDataSourceInitialized();\r\n const userRepo = dataSource.getRepository(CMS_ENTITY_MAP.users);\r\n return getNextAuthOptions({\r\n getUserByEmail: async (email: string) => {\r\n return userRepo.findOne({\r\n where: { email },\r\n relations: ['group', 'group.permissions'],\r\n select: ['id', 'email', 'name', 'password', 'blocked', 'deleted', 'groupId', 'adminAccess'],\r\n }) as any;\r\n },\r\n comparePassword: (plain, hash) => Promise.resolve(bcrypt.compareSync(plain, hash)),\r\n signInPage: '/admin/signin',\r\n });\r\n}\r\n\r\nlet handler: ReturnType<typeof NextAuth> | null = null;\r\n\r\nasync function getHandler() {\r\n if (!handler) handler = NextAuth(await getOptions());\r\n return handler;\r\n}\r\n\r\ntype NextAuthContext = { params: Promise<{ nextauth?: string[] }> };\r\n\r\nexport async function GET(req: Request, context: NextAuthContext) {\r\n return (await getHandler())(req, context);\r\n}\r\nexport async function POST(req: Request, context: NextAuthContext) {\r\n return (await getHandler())(req, context);\r\n}\r\n`,\r\n\r\n 'src/app/admin/layout.tsx': `'use client';\r\n\r\nimport '@infuro/cms-core/admin.css';\r\nimport AdminLayout from '@infuro/cms-core/admin';\r\n\r\nexport default function AdminLayoutWrapper({ children }: { children: React.ReactNode }) {\r\n return (\r\n <AdminLayout\r\n customNavItems={[]}\r\n customNavSections={[]}\r\n customCrudConfigs={{}}\r\n >\r\n {children}\r\n </AdminLayout>\r\n );\r\n}\r\n`,\r\n\r\n 'src/app/admin/[[...slug]]/page.tsx': `import { AdminPageResolver } from '@infuro/cms-core/admin';\r\n\r\nexport default async function AdminPage({ params }: { params: Promise<{ slug?: string[] }> }) {\r\n const { slug } = await params;\r\n return <AdminPageResolver slug={slug} />;\r\n}\r\n`,\r\n\r\n 'src/middleware.ts': `import { NextResponse } from 'next/server';\r\nimport type { NextRequest } from 'next/server';\r\nimport { createCmsMiddleware } from '@infuro/cms-core/auth';\r\n\r\nconst cmsMiddleware = createCmsMiddleware({\r\n publicApiMethods: {\r\n '/api/contacts': ['POST'],\r\n '/api/form-submissions': ['POST'],\r\n '/api/blogs': ['GET'],\r\n '/api/forms': ['GET'],\r\n '/api/auth': ['GET', 'POST'],\r\n '/api/users/forgot-password': ['POST'],\r\n '/api/users/set-password': ['POST'],\r\n '/api/users/invite': ['POST'],\r\n },\r\n});\r\n\r\nexport function middleware(request: NextRequest) {\r\n const result = cmsMiddleware({\r\n nextUrl: request.nextUrl,\r\n url: request.url,\r\n method: request.method,\r\n cookies: request.cookies,\r\n });\r\n\r\n if (result.type === 'next') return NextResponse.next();\r\n if (result.type === 'redirect') return NextResponse.redirect(result.url);\r\n if (result.type === 'json') return NextResponse.json(result.body, { status: result.status });\r\n return NextResponse.next();\r\n}\r\n\r\nexport const config = {\r\n matcher: ['/admin/:path*', '/api/:path*'],\r\n};\r\n`,\r\n\r\n 'src/app/providers.tsx': `\"use client\";\r\n\r\nimport { ThemeProvider } from \"next-themes\";\r\nimport { SessionProvider } from \"next-auth/react\";\r\nimport { Toaster } from \"sonner\";\r\n\r\nexport function Providers({ children }: { children: React.ReactNode }) {\r\n return (\r\n <SessionProvider>\r\n <ThemeProvider attribute=\"class\" defaultTheme=\"system\" enableSystem>\r\n {children}\r\n <Toaster position=\"top-right\" />\r\n </ThemeProvider>\r\n </SessionProvider>\r\n );\r\n}\r\n`,\r\n\r\n '.env.example': `DATABASE_URL=postgres://user:password@localhost:5432/mydb\r\nNEXTAUTH_SECRET=your-random-secret\r\nNEXTAUTH_URL=http://localhost:3000\r\n\r\n# Admin user (for npm run seed)\r\nADMIN_EMAIL=admin@example.com\r\nADMIN_PASSWORD=changeme\r\n`,\r\n\r\n 'src/lib/seed.ts': `try { require('dotenv/config'); } catch {}\r\nimport 'reflect-metadata';\r\nimport { getDataSourceInitialized } from './data-source';\r\nimport { CMS_ENTITY_MAP } from '@infuro/cms-core';\r\nimport bcrypt from 'bcryptjs';\r\n\r\nasync function main() {\r\n const ds = await getDataSourceInitialized();\r\n const userRepo = ds.getRepository(CMS_ENTITY_MAP.users);\r\n\r\n const email = process.env.ADMIN_EMAIL || 'admin@example.com';\r\n const password = process.env.ADMIN_PASSWORD || 'changeme';\r\n\r\n const existing = await userRepo.findOne({ where: { email } });\r\n if (!existing) {\r\n const hashedPassword = await bcrypt.hash(password, 10);\r\n await userRepo.save(userRepo.create({ name: 'Admin', email, password: hashedPassword }));\r\n console.log('Default admin user created');\r\n } else {\r\n console.log('Default admin user already exists');\r\n }\r\n\r\n await ds.destroy();\r\n}\r\n\r\nmain().catch((e) => {\r\n console.error(e);\r\n process.exit(1);\r\n});\r\n`,\r\n\r\n 'scripts/migration-datasource.cjs': `/**\r\n * Data source for TypeORM CLI (migration:run / migration:revert).\r\n * Loads app migrations plus core migrations from node_modules/@infuro/cms-core/dist/migrations.\r\n */\r\nrequire('reflect-metadata');\r\nrequire('dotenv/config');\r\nconst fs = require('fs');\r\nconst path = require('path');\r\nconst { DataSource } = require('typeorm');\r\n\r\nconst root = path.resolve(__dirname, '..');\r\nconst coreEntry = path.join(root, 'node_modules', '@infuro', 'cms-core', 'dist', 'index.cjs');\r\nconst { CMS_ENTITY_MAP } = require(coreEntry);\r\n\r\nfunction migrationGlobs() {\r\n const globs = [path.join(root, 'src', 'migrations', '*.ts')];\r\n const coreDist = path.join(root, 'node_modules', '@infuro', 'cms-core', 'dist', 'migrations');\r\n if (fs.existsSync(coreDist)) {\r\n globs.push(path.join(coreDist, '*.js'));\r\n globs.push(path.join(coreDist, '*.ts'));\r\n }\r\n return globs;\r\n}\r\n\r\nmodule.exports = new DataSource({\r\n type: 'postgres',\r\n url: process.env.DATABASE_URL,\r\n entities: Object.values(CMS_ENTITY_MAP),\r\n synchronize: false,\r\n migrations: migrationGlobs(),\r\n});\r\n`,\r\n\r\n 'scripts/run-migrations.ts': `/**\r\n * Run TypeORM migrations. Loads .env so DATABASE_URL is set.\r\n * Usage: npm run migration:run\r\n */\r\ntry { require('dotenv/config'); } catch {}\r\nimport 'reflect-metadata';\r\n\r\nasync function main() {\r\n process.env.TYPEORM_CLI = '1';\r\n const { getDataSourceInitialized } = await import('../src/lib/data-source');\r\n const ds = await getDataSourceInitialized();\r\n const run = await ds.runMigrations();\r\n console.log(run.length ? \\`Ran \\${run.length} migration(s).\\` : 'No pending migrations.');\r\n await ds.destroy();\r\n}\r\n\r\nmain().catch((e) => {\r\n console.error(e);\r\n process.exit(1);\r\n});\r\n`,\r\n\r\n 'src/migrations/README.md': `# TypeORM migrations\r\n\r\nGenerate a new migration (after changing entities):\r\n\r\n\\`\\`\\`bash\r\nnpm run migration:generate -- MyMigrationName\r\n\\`\\`\\`\r\n\r\nRun pending migrations:\r\n\r\n\\`\\`\\`bash\r\nnpm run migration:run\r\n\\`\\`\\`\r\n`,\r\n\r\n 'src/themes/default/index.ts': `import { createTheme } from '@infuro/cms-core/theme';\r\n\r\nimport { Container, meta as containerMeta } from './components/Container';\r\nimport { TextBlock, meta as textBlockMeta } from './components/TextBlock';\r\n\r\nimport { Navbar } from './layout/Navbar';\r\nimport { Footer, footerFields, footerDefaults } from './layout/Footer';\r\n\r\nexport default createTheme({\r\n name: 'default',\r\n label: 'Default Theme',\r\n components: [\r\n { component: Container, meta: containerMeta },\r\n { component: TextBlock, meta: textBlockMeta },\r\n ],\r\n layout: {\r\n navbar: { component: Navbar },\r\n footer: { component: Footer, fields: footerFields, defaults: footerDefaults },\r\n },\r\n});\r\n`,\r\n\r\n 'src/themes/default/layout/Navbar.tsx': `import type { NavbarConfig, NavItem } from '@infuro/cms-core/theme';\r\n\r\nfunction NavLink({ item }: { item: NavItem }) {\r\n return (\r\n <li className=\"list-none\">\r\n <a\r\n href={item.url}\r\n target={item.openInNewTab ? '_blank' : undefined}\r\n rel={item.openInNewTab ? 'noopener noreferrer' : undefined}\r\n className=\"text-sm font-medium text-gray-700 hover:text-gray-900 px-3 py-2 inline-block\"\r\n >\r\n {item.label}\r\n </a>\r\n </li>\r\n );\r\n}\r\n\r\nexport function Navbar({ logo, items, ctaLabel, ctaUrl }: NavbarConfig) {\r\n return (\r\n <nav className=\"bg-white sticky top-0 z-40 border-b\">\r\n <div className=\"max-w-7xl mx-auto px-4 sm:px-6 lg:px-8\">\r\n <div className=\"flex items-center justify-between h-16\">\r\n <a href=\"/\" className=\"flex-shrink-0\">\r\n {logo ? (\r\n <img src={logo} alt=\"Logo\" className=\"h-8\" />\r\n ) : (\r\n <span className=\"text-xl font-bold text-gray-900\">Logo</span>\r\n )}\r\n </a>\r\n <ul className=\"flex items-center gap-1 list-none m-0 p-0\">\r\n {items.map((item) => (\r\n <NavLink key={item.id} item={item} />\r\n ))}\r\n </ul>\r\n {ctaLabel && (\r\n <a href={ctaUrl || '#'} className=\"text-sm font-medium text-gray-900 hover:underline\">\r\n {ctaLabel}\r\n </a>\r\n )}\r\n </div>\r\n </div>\r\n </nav>\r\n );\r\n}\r\n`,\r\n\r\n 'src/themes/default/layout/Footer.tsx': `import type { FooterConfig, PropDefinition } from '@infuro/cms-core/theme';\r\n\r\nexport const footerFields: PropDefinition[] = [\r\n { name: 'copyright', label: 'Copyright Text', type: 'text' },\r\n];\r\n\r\nexport const footerDefaults: Record<string, any> = {\r\n copyright: '© 2025 Your Site. All rights reserved.',\r\n columns: [],\r\n socialLinks: [],\r\n};\r\n\r\nexport function Footer({ copyright, columns = [], socialLinks = [] }: FooterConfig) {\r\n return (\r\n <footer className=\"bg-gray-900 text-gray-400 py-8 px-8\">\r\n <div className=\"max-w-7xl mx-auto\">\r\n {columns.length > 0 && (\r\n <div className=\"grid grid-cols-2 md:grid-cols-4 gap-8 mb-6\">\r\n {columns.map((col, i) => (\r\n <div key={i}>\r\n <h4 className=\"text-white font-semibold mb-3 text-sm\">{col.title}</h4>\r\n <ul className=\"space-y-2\">\r\n {col.links.map((link, j) => (\r\n <li key={j}>\r\n <a href={link.url} className=\"text-sm hover:text-white transition-colors\">\r\n {link.label}\r\n </a>\r\n </li>\r\n ))}\r\n </ul>\r\n </div>\r\n ))}\r\n </div>\r\n )}\r\n <div className=\"border-t border-gray-800 pt-6\">\r\n <p className=\"text-sm\">{copyright}</p>\r\n </div>\r\n </div>\r\n </footer>\r\n );\r\n}\r\n`,\r\n\r\n 'src/themes/default/components/Container.tsx': `import type { ComponentMeta } from '@infuro/cms-core/theme';\r\n\r\nexport const meta: ComponentMeta = {\r\n name: 'Container',\r\n label: 'Container',\r\n category: 'layout',\r\n icon: 'LayoutDashboard',\r\n description: 'A layout container that holds other components',\r\n defaultProps: { background: '#ffffff' },\r\n props: [{ name: 'background', label: 'Background', type: 'color' }],\r\n canContainChildren: true,\r\n};\r\n\r\nexport function Container({\r\n background = '#ffffff',\r\n children,\r\n}: {\r\n background?: string;\r\n children?: React.ReactNode;\r\n}) {\r\n return (\r\n <div style={{ backgroundColor: background, minHeight: '48px', padding: '1rem' }}>\r\n {children}\r\n </div>\r\n );\r\n}\r\n`,\r\n\r\n 'src/themes/default/components/TextBlock.tsx': `import type { ComponentMeta } from '@infuro/cms-core/theme';\r\n\r\nexport const meta: ComponentMeta = {\r\n name: 'TextBlock',\r\n label: 'Text Block',\r\n category: 'content',\r\n icon: 'Type',\r\n description: 'Rich text content block',\r\n defaultProps: { content: '<p>Enter your text here...</p>' },\r\n props: [{ name: 'content', label: 'Content', type: 'richtext' }],\r\n};\r\n\r\nexport function TextBlock({ content }: { content?: string }) {\r\n return (\r\n <div\r\n className=\"prose prose-gray max-w-none py-4 px-2\"\r\n dangerouslySetInnerHTML={{ __html: content || '' }}\r\n />\r\n );\r\n}\r\n`,\r\n\r\n 'src/lib/theme-registry.ts': `import defaultTheme from '@/themes/default';\r\nimport type { ThemeConfig } from '@infuro/cms-core/theme';\r\n\r\nexport const defaultThemeConfig = defaultTheme;\r\n\r\nexport interface ThemeRegistryItem {\r\n id: string;\r\n label: string;\r\n config: ThemeConfig;\r\n description?: string;\r\n}\r\n\r\nexport const THEME_REGISTRY: ThemeRegistryItem[] = [\r\n {\r\n id: 'default',\r\n label: 'Default',\r\n config: defaultTheme,\r\n description: 'Default theme with standard layout and components.',\r\n },\r\n];\r\n\r\nexport function getThemeById(id: string | undefined): ThemeConfig {\r\n const theme = THEME_REGISTRY.find((t) => t.id === (id || '')) ?? THEME_REGISTRY[0];\r\n return theme?.config ?? defaultTheme;\r\n}\r\n`,\r\n\r\n 'src/app/page.tsx': `export default function HomePage() {\r\n return (\r\n <main className=\"min-h-screen flex flex-col items-center justify-center p-8\">\r\n <h1 className=\"text-3xl font-bold text-gray-900 mb-4\">Welcome</h1>\r\n <p className=\"text-gray-600 mb-6\">Your CMS is set up. Manage content at the admin panel.</p>\r\n <a\r\n href=\"/admin\"\r\n className=\"text-white bg-gray-900 hover:bg-gray-800 px-4 py-2 rounded-lg font-medium transition-colors\"\r\n >\r\n Open Admin\r\n </a>\r\n </main>\r\n );\r\n}\r\n`,\r\n\r\n 'src/app/contact/page.tsx': `export default function ContactPage() {\r\n return (\r\n <main className=\"min-h-screen p-8 max-w-2xl mx-auto\">\r\n <h1 className=\"text-3xl font-bold text-gray-900 mb-4\">Contact</h1>\r\n <p className=\"text-gray-600\">\r\n Add a contact form or wire this page to your CMS form. Use the admin panel to manage forms and submissions.\r\n </p>\r\n </main>\r\n );\r\n}\r\n`,\r\n};\r\n\r\nfunction findRoot(cwd: string): string | null {\r\n let dir = path.resolve(cwd);\r\n for (let i = 0; i < 20; i++) {\r\n const pkgPath = path.join(dir, 'package.json');\r\n if (fs.existsSync(pkgPath)) {\r\n try {\r\n const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));\r\n if (pkg.dependencies?.next || pkg.devDependencies?.next) return dir;\r\n } catch {\r\n // ignore\r\n }\r\n }\r\n const parent = path.dirname(dir);\r\n if (parent === dir) break;\r\n dir = parent;\r\n }\r\n return null;\r\n}\r\n\r\nfunction writeFile(\r\n root: string,\r\n filePath: string,\r\n content: string,\r\n force: boolean,\r\n dryRun: boolean,\r\n log: (msg: string) => void\r\n): boolean {\r\n const full = path.join(root, filePath);\r\n if (fs.existsSync(full) && !force) {\r\n log(` skip (exists): ${filePath}`);\r\n return false;\r\n }\r\n if (dryRun) {\r\n log(` would create: ${filePath}`);\r\n return true;\r\n }\r\n const dir = path.dirname(full);\r\n fs.mkdirSync(dir, { recursive: true });\r\n fs.writeFileSync(full, content, 'utf8');\r\n log(` created: ${filePath}`);\r\n return true;\r\n}\r\n\r\nfunction patchNextConfig(root: string, dryRun: boolean, log: (msg: string) => void): boolean {\r\n const candidates = ['next.config.js', 'next.config.mjs', 'next.config.cjs'];\r\n let configPath: string | null = null;\r\n for (const name of candidates) {\r\n const p = path.join(root, name);\r\n if (fs.existsSync(p)) {\r\n configPath = p;\r\n break;\r\n }\r\n }\r\n if (!configPath) {\r\n log(' skip next.config: not found');\r\n return false;\r\n }\r\n let content = fs.readFileSync(configPath, 'utf8');\r\n if (content.includes(\"'@infuro/cms-core'\") || content.includes('\"@infuro/cms-core\"')) {\r\n log(` skip (already has core): ${path.basename(configPath)}`);\r\n return false;\r\n }\r\n if (content.includes('serverExternalPackages')) {\r\n content = content.replace(\r\n /(serverExternalPackages:\\s*\\[)/,\r\n \"$1'@infuro/cms-core', 'typeorm', 'rss-parser', \"\r\n );\r\n } else {\r\n content = content.replace(\r\n /(const nextConfig\\s*=\\s*\\{|module\\.exports\\s*=\\s*\\{)/,\r\n \"$1\\n serverExternalPackages: ['@infuro/cms-core', 'typeorm', 'rss-parser'],\"\r\n );\r\n }\r\n if (dryRun) {\r\n log(` would patch: ${path.basename(configPath)}`);\r\n return true;\r\n }\r\n fs.writeFileSync(configPath, content, 'utf8');\r\n log(` patched: ${path.basename(configPath)}`);\r\n return true;\r\n}\r\n\r\nfunction patchTailwind(root: string, dryRun: boolean, log: (msg: string) => void): boolean {\r\n const candidates = ['tailwind.config.js', 'tailwind.config.mjs', 'tailwind.config.ts'];\r\n let configPath: string | null = null;\r\n for (const name of candidates) {\r\n const p = path.join(root, name);\r\n if (fs.existsSync(p)) {\r\n configPath = p;\r\n break;\r\n }\r\n }\r\n if (!configPath) {\r\n log(' skip tailwind: config not found');\r\n return false;\r\n }\r\n let content = fs.readFileSync(configPath, 'utf8');\r\n const coreContent = \"./node_modules/@infuro/cms-core/dist/**/*.{js,cjs}\";\r\n if (content.includes('@infuro/cms-core')) {\r\n log(` skip (already has core): ${path.basename(configPath)}`);\r\n return false;\r\n }\r\n if (content.includes('content:')) {\r\n content = content.replace(\r\n /(content:\\s*\\[)/,\r\n `$1\\n \"${coreContent}\",`\r\n );\r\n }\r\n if (dryRun) {\r\n log(` would patch: ${path.basename(configPath)}`);\r\n return true;\r\n }\r\n fs.writeFileSync(configPath, content, 'utf8');\r\n log(` patched: ${path.basename(configPath)}`);\r\n return true;\r\n}\r\n\r\nfunction patchLayout(root: string, dryRun: boolean, log: (msg: string) => void): boolean {\r\n const layoutPath = path.join(root, 'src/app/layout.tsx');\r\n if (!fs.existsSync(layoutPath)) {\r\n log(' skip layout: src/app/layout.tsx not found');\r\n return false;\r\n }\r\n let content = fs.readFileSync(layoutPath, 'utf8');\r\n if (content.includes('<Providers>')) {\r\n log(' skip layout: already uses Providers');\r\n return false;\r\n }\r\n const bodyMatch = content.match(/<body([^>]*)>\\s*(\\{children\\})\\s*<\\/body>/s);\r\n if (!bodyMatch) {\r\n log(' skip layout: unexpected structure (add <Providers> manually)');\r\n return false;\r\n }\r\n if (dryRun) {\r\n log(' would patch: src/app/layout.tsx');\r\n return true;\r\n }\r\n const [, bodyAttrs, children] = bodyMatch;\r\n const newBody = `<body${bodyAttrs}>\\n <Providers>${children}</Providers>\\n </body>`;\r\n content = content.replace(/<body[^>]*>\\s*\\{children\\}\\s*<\\/body>/s, newBody);\r\n if (!content.includes(\"from './providers'\") && !content.includes('from \"./providers\"')) {\r\n const firstImport = content.match(/^import .+ from .+;\\n/m);\r\n content = firstImport\r\n ? content.replace(firstImport[0], firstImport[0] + \"import { Providers } from './providers';\\n\")\r\n : \"import { Providers } from './providers';\\n\" + content;\r\n }\r\n if (content.includes('<html') && !content.includes('suppressHydrationWarning')) {\r\n content = content.replace(/<html(\\s)/, '<html suppressHydrationWarning$1');\r\n }\r\n fs.writeFileSync(layoutPath, content, 'utf8');\r\n log(' patched: src/app/layout.tsx');\r\n return true;\r\n}\r\n\r\nfunction patchPackageJson(root: string, dryRun: boolean, log: (msg: string) => void): boolean {\r\n const pkgPath = path.join(root, 'package.json');\r\n if (!fs.existsSync(pkgPath)) return false;\r\n const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));\r\n const scripts = pkg.scripts || {};\r\n let changed = false;\r\n if (!scripts.seed) {\r\n scripts.seed = 'tsx src/lib/seed.ts';\r\n changed = true;\r\n }\r\n if (!scripts['migration:run']) {\r\n scripts['migration:run'] = 'tsx scripts/run-migrations.ts';\r\n changed = true;\r\n }\r\n const dev = pkg.devDependencies || {};\r\n const deps = pkg.dependencies || {};\r\n if (!deps['@infuro/cms-core']) {\r\n deps['@infuro/cms-core'] = '^1.0.6';\r\n changed = true;\r\n }\r\n if (!dev.tsx) {\r\n dev.tsx = '^4.0.0';\r\n changed = true;\r\n }\r\n if (!dev.dotenv) {\r\n dev.dotenv = '^16.0.0';\r\n changed = true;\r\n }\r\n if (!changed) {\r\n log(' skip package.json: scripts/devDeps already present');\r\n return false;\r\n }\r\n pkg.scripts = scripts;\r\n pkg.dependencies = { ...pkg.dependencies, ...deps };\r\n pkg.devDependencies = { ...pkg.devDependencies, ...dev };\r\n if (dryRun) {\r\n log(' would patch: package.json (scripts + devDependencies)');\r\n return true;\r\n }\r\n fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2), 'utf8');\r\n log(' patched: package.json');\r\n return true;\r\n}\r\n\r\nasync function runNpmInstall(root: string, log: (msg: string) => void): Promise<void> {\r\n const { spawnSync } = await import('child_process');\r\n log(' running: npm install (deps)...');\r\n spawnSync('npm', ['install', '@infuro/cms-core', 'typeorm', 'reflect-metadata', 'bcryptjs', 'next-auth', 'next-themes', 'sonner'], {\r\n cwd: root,\r\n stdio: 'inherit',\r\n shell: true,\r\n });\r\n log(' running: npm install -D tsx dotenv @types/node...');\r\n spawnSync('npm', ['install', '-D', 'tsx', 'dotenv', '@types/node'], {\r\n cwd: root,\r\n stdio: 'inherit',\r\n shell: true,\r\n });\r\n}\r\n\r\nasync function runInit(opts: {\r\n force: boolean;\r\n dryRun: boolean;\r\n noDeps: boolean;\r\n noPatchConfig: boolean;\r\n}) {\r\n const log = (msg: string) => console.log(msg);\r\n const cwd = process.cwd();\r\n const root = findRoot(cwd);\r\n if (!root) {\r\n console.error('Not a Next.js project (no package.json with next dependency found from ' + cwd + ')');\r\n process.exit(1);\r\n }\r\n const appDir = path.join(root, 'src/app');\r\n if (!fs.existsSync(appDir)) {\r\n console.error('Expected src/app directory not found. Use a Next.js app with src directory (e.g. create-next-app --src-dir).');\r\n process.exit(1);\r\n }\r\n\r\n log('Infuro CMS init @ ' + root);\r\n if (opts.dryRun) log('(dry run)');\r\n\r\n for (const [filePath, content] of Object.entries(TEMPLATES)) {\r\n writeFile(root, filePath, content, opts.force, opts.dryRun, log);\r\n }\r\n\r\n if (!opts.noPatchConfig) {\r\n log('Config patches:');\r\n patchNextConfig(root, opts.dryRun, log);\r\n patchTailwind(root, opts.dryRun, log);\r\n patchLayout(root, opts.dryRun, log);\r\n patchPackageJson(root, opts.dryRun, log);\r\n }\r\n\r\n if (!opts.noDeps && !opts.dryRun) {\r\n log('Dependencies:');\r\n await runNpmInstall(root, log);\r\n } else if (!opts.noDeps && opts.dryRun) {\r\n log(' would run: npm install @infuro/cms-core typeorm reflect-metadata bcryptjs next-auth next-themes sonner');\r\n log(' would run: npm install -D tsx dotenv @types/node');\r\n }\r\n\r\n log('');\r\n log('Done. Next steps:');\r\n log(' 1. Copy .env.example to .env and set DATABASE_URL, NEXTAUTH_SECRET, NEXTAUTH_URL, ADMIN_EMAIL, ADMIN_PASSWORD');\r\n log(' 2. Run npm run migration:run then npm run seed (creates admin from ADMIN_EMAIL/ADMIN_PASSWORD)');\r\n log(' 3. npm run dev');\r\n}\r\n\r\nconst args = process.argv.slice(2);\r\nconst force = args.includes('--force');\r\nconst dryRun = args.includes('--dry-run');\r\nconst noDeps = args.includes('--no-deps');\r\nconst noPatchConfig = args.includes('--no-patch-config');\r\n\r\nif (args[0] === 'init' || args.includes('--init') || (args.length === 0 && !args.some((a) => a.startsWith('--')))) {\r\n runInit({ force, dryRun, noDeps, noPatchConfig }).catch((e) => {\r\n console.error(e);\r\n process.exit(1);\r\n });\r\n} else {\r\n console.log('Usage: npx @infuro/cms-core init [--force] [--dry-run] [--no-deps] [--no-patch-config]');\r\n process.exit(args[0] === '--help' || args[0] === '-h' ? 0 : 1);\r\n}\r\n"],"mappings":"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","names":["force","dryRun"]}
@@ -341,6 +341,8 @@ interface SettingsApiConfig extends CmsHandlersBase {
341
341
  /** Groups in this list are readable without auth (GET returns all keys for the group). */
342
342
  publicGetGroups?: string[];
343
343
  }
344
+ declare function simpleEncrypt(text: string, key: string): string;
345
+ declare function simpleDecrypt(encoded: string, key: string): string;
344
346
  /**
345
347
  * Structural types so apps with their own `typeorm` install (e.g. npm link) typecheck without duplicate-package errors.
346
348
  */
@@ -525,4 +527,4 @@ declare function createStorefrontApiHandler(config: StorefrontApiConfig): {
525
527
  handle(method: string, path: string[], req: Request): Promise<Response>;
526
528
  };
527
529
 
528
- export { createUserAvatarHandler as $, type AnalyticsHandlerConfig as A, type BlogBySlugConfig as B, type CompanyDetails as C, type DashboardStatsConfig as D, type EmailTemplateResult as E, type ForgotPasswordConfig as F, type GetPublicSettingsGroupConfig as G, createCrudHandler as H, type InviteAcceptConfig as I, createDashboardStatsHandler as J, createEcommerceAnalyticsHandler as K, type LlmAgentKnowledgeApiConfig as L, createForgotPasswordHandler as M, createFormBySlugHandler as N, type OrderPlacedLineItem as O, type ParsedLlmAgentValidation as P, createInviteAcceptHandler as Q, createLlmAgentKnowledgeHandlers as R, type StorageService as S, type TemplateContext as T, type UploadHandlerConfig as U, createMediaZipExtractHandler as V, createSetPasswordHandler as W, createSettingsApiHandlers as X, createStorefrontApiHandler as Y, createUploadHandler as Z, createUserAuthApiRouter as _, type EmailTemplateName as a, createUserProfileHandler as a0, createUsersApiHandlers as a1, getCompanyDetailsFromSettings as a2, getPublicSettingsGroup as a3, mergeEmailLayoutCompanyDetails as a4, mergeGuardrailsIntoSystemPrompt as a5, parseLlmAgentValidationRules as a6, validateUserMessageAgainstAgentRules as a7, validateUserMessageAgainstStructuredRules as a8, type EntityMap as b, type AuthHandlersConfig as c, type ChangePasswordConfig as d, type ChatPublicConfig as e, type CmsApiHandlerConfig as f, type CmsGetter as g, type CrudHandlerOptions as h, type EcommerceAnalyticsConfig as i, type FormBySlugConfig as j, type GetPublicSettingsGroupDataSource as k, type LlmAgentValidationRulesJson as l, type SetPasswordConfig as m, type SettingsApiConfig as n, type SocialLinkItem as o, type StorefrontApiConfig as p, type StorefrontOtpFlags as q, type UserAuthApiConfig as r, type UserAvatarConfig as s, type UserProfileConfig as t, type UsersApiConfig as u, createAnalyticsHandlers as v, createBlogBySlugHandler as w, createChangePasswordHandler as x, createCmsApiHandler as y, createCrudByIdHandler as z };
530
+ export { createUserAvatarHandler as $, type AnalyticsHandlerConfig as A, type BlogBySlugConfig as B, type CompanyDetails as C, type DashboardStatsConfig as D, type EmailTemplateResult as E, type ForgotPasswordConfig as F, type GetPublicSettingsGroupConfig as G, createCrudHandler as H, type InviteAcceptConfig as I, createDashboardStatsHandler as J, createEcommerceAnalyticsHandler as K, type LlmAgentKnowledgeApiConfig as L, createForgotPasswordHandler as M, createFormBySlugHandler as N, type OrderPlacedLineItem as O, type ParsedLlmAgentValidation as P, createInviteAcceptHandler as Q, createLlmAgentKnowledgeHandlers as R, type StorageService as S, type TemplateContext as T, type UploadHandlerConfig as U, createMediaZipExtractHandler as V, createSetPasswordHandler as W, createSettingsApiHandlers as X, createStorefrontApiHandler as Y, createUploadHandler as Z, createUserAuthApiRouter as _, type EmailTemplateName as a, createUserProfileHandler as a0, createUsersApiHandlers as a1, getCompanyDetailsFromSettings as a2, getPublicSettingsGroup as a3, mergeEmailLayoutCompanyDetails as a4, mergeGuardrailsIntoSystemPrompt as a5, parseLlmAgentValidationRules as a6, simpleDecrypt as a7, simpleEncrypt as a8, validateUserMessageAgainstAgentRules as a9, validateUserMessageAgainstStructuredRules as aa, type EntityMap as b, type AuthHandlersConfig as c, type ChangePasswordConfig as d, type ChatPublicConfig as e, type CmsApiHandlerConfig as f, type CmsGetter as g, type CrudHandlerOptions as h, type EcommerceAnalyticsConfig as i, type FormBySlugConfig as j, type GetPublicSettingsGroupDataSource as k, type LlmAgentValidationRulesJson as l, type SetPasswordConfig as m, type SettingsApiConfig as n, type SocialLinkItem as o, type StorefrontApiConfig as p, type StorefrontOtpFlags as q, type UserAuthApiConfig as r, type UserAvatarConfig as s, type UserProfileConfig as t, type UsersApiConfig as u, createAnalyticsHandlers as v, createBlogBySlugHandler as w, createChangePasswordHandler as x, createCmsApiHandler as y, createCrudByIdHandler as z };