#!/usr/bin/env node /** * Zen Setup CLI * Command-line tool for setting up Zen in a Next.js project */ import { mkdir, writeFile } from 'node:fs/promises'; import { existsSync } from 'node:fs'; import { resolve, dirname } from 'node:path'; import readline from 'readline'; // File templates const templates = { instrumentation: `// instrumentation.js export async function register() { if (process.env.NEXT_RUNTIME === 'nodejs') { const { initializeZen } = await import('@zen/core'); await initializeZen(); } } `, authRedirect: `import { redirect } from 'next/navigation'; export default function Redirect() { redirect('/auth/login/'); } `, authCatchAll: `export { default } from '@zen/core/auth/page'; `, adminRedirect: `import { redirect } from 'next/navigation'; export default function Redirect() { redirect('/admin/dashboard'); } `, adminCatchAll: `export { default } from '@zen/core/admin/page'; `, zenApiRoute: `export { GET, POST, PUT, DELETE, PATCH } from '@zen/core/zen/api'; `, zenPageRoute: `export { default, generateMetadata } from '@zen/core/modules/page'; `, nextConfig: `// next.config.js module.exports = { experimental: { instrumentationHook: true, }, }; `, }; // File definitions const files = [ { path: 'instrumentation.js', template: 'instrumentation', description: 'Instrumentation file (initialize Zen)', }, { path: 'app/(auth)/auth/page.js', template: 'authRedirect', description: 'Auth redirect page', }, { path: 'app/(auth)/auth/[...auth]/page.js', template: 'authCatchAll', description: 'Auth catch-all route', }, { path: 'app/(admin)/admin/page.js', template: 'adminRedirect', description: 'Admin redirect page', }, { path: 'app/(admin)/admin/[...admin]/page.js', template: 'adminCatchAll', description: 'Admin catch-all route', }, { path: 'app/zen/api/[...path]/route.js', template: 'zenApiRoute', description: 'Zen API catch-all route', }, { path: 'app/zen/[...zen]/page.js', template: 'zenPageRoute', description: 'Zen public pages catch-all route', }, ]; async function createFile(filePath, content, force = false) { const fullPath = resolve(process.cwd(), filePath); // Check if file already exists if (existsSync(fullPath) && !force) { console.log(`ā­ļø Skipped (already exists): ${filePath}`); return { created: false, skipped: true }; } // Create directory if it doesn't exist const dir = dirname(fullPath); await mkdir(dir, { recursive: true }); // Write the file await writeFile(fullPath, content, 'utf-8'); console.log(`āœ… Created: ${filePath}`); return { created: true, skipped: false }; } async function setupZen(options = {}) { const { force = false } = options; console.log('šŸš€ Setting up Zen for your Next.js project...\n'); let created = 0; let skipped = 0; for (const file of files) { const result = await createFile( file.path, templates[file.template], force ); if (result.created) created++; if (result.skipped) skipped++; } console.log('\nšŸ“ Summary:'); console.log(` āœ… Created: ${created} file${created !== 1 ? 's' : ''}`); console.log(` ā­ļø Skipped: ${skipped} file${skipped !== 1 ? 's' : ''}`); // Check if next.config.js needs updating const nextConfigPath = resolve(process.cwd(), 'next.config.js'); const nextConfigExists = existsSync(nextConfigPath); if (!nextConfigExists) { console.log('\nāš ļø Note: next.config.js not found.'); console.log(' Make sure to enable instrumentation in your Next.js config:'); console.log(' experimental: { instrumentationHook: true }'); } console.log('\nšŸŽ‰ Setup complete!'); console.log('\nNext steps:'); console.log(' 1. Add Zen styles to your globals.css:'); console.log(' @import \'@zen/core/styles/zen.css\';'); console.log(' 2. Configure environment variables (see .env.example)'); console.log(' 3. Initialize the database:'); console.log(' npx zen-db init'); console.log('\nFor more information, check the INSTALL.md file.'); } async function listFiles() { console.log('šŸ“‹ Files that will be created:\n'); for (const file of files) { const exists = existsSync(resolve(process.cwd(), file.path)); const status = exists ? 'āœ“ exists' : 'āœ— missing'; console.log(` ${status} ${file.path}`); console.log(` ${file.description}`); } console.log('\nRun "npx zen-setup init" to create missing files.'); } async function runCLI() { const command = process.argv[2]; const flags = process.argv.slice(3); const force = flags.includes('--force') || flags.includes('-f'); if (!command || command === 'help') { console.log(` Zen Setup CLI Usage: npx zen-setup [options] Commands: init Create all required files for Zen setup list List all files that will be created help Show this help message Options: --force, -f Force overwrite existing files Examples: npx zen-setup init # Create missing files npx zen-setup init --force # Overwrite all files npx zen-setup list # List all files `); process.exit(0); } try { switch (command) { case 'init': if (force) { console.log('āš ļø WARNING: --force flag will overwrite existing files!\n'); console.log('Type "yes" to confirm or Ctrl+C to cancel...'); const rl = readline.createInterface({ input: process.stdin, output: process.stdout, }); rl.question('Confirm (yes/no): ', async (answer) => { if (answer.toLowerCase() === 'yes') { await setupZen({ force: true }); } else { console.log('āŒ Operation cancelled.'); } rl.close(); process.exit(0); }); return; // Don't exit yet } else { await setupZen({ force: false }); } break; case 'list': await listFiles(); break; default: console.log(`āŒ Unknown command: ${command}`); console.log('Run "npx zen-setup help" for usage information.'); process.exit(1); } process.exit(0); } catch (error) { console.error('āŒ Error:', error.message); console.error(error.stack); process.exit(1); } } // Run CLI if called directly import { fileURLToPath } from 'url'; import { realpathSync } from 'node:fs'; const __filename = realpathSync(fileURLToPath(import.meta.url)); const isMainModule = process.argv[1] && realpathSync(process.argv[1]) === __filename; if (isMainModule) { runCLI(); } export { runCLI, setupZen };