UNPKG

@rotosaurio/sysrot-core

Version:

CLI de nueva generación para proyectos Next.js 14+ con IA multi-modelo, Web3 integration, internacionalización completa y roadmap realista 2025-2026

590 lines (493 loc) 17.9 kB
const fs = require('fs-extra'); const path = require('path'); const chalk = require('chalk'); const { execSync } = require('child_process'); const ora = require('ora'); async function createProject(options) { const { projectName } = options; const projectPath = path.resolve(process.cwd(), projectName); // Verificar si la carpeta ya existe if (fs.existsSync(projectPath)) { console.error(chalk.red(`La carpeta ${projectName} ya existe. Por favor elige otro nombre o elimina la carpeta existente.`)); process.exit(1); } console.log(chalk.green('✅ Generando tu proyecto...')); console.log(chalk.blue(`📁 Proyecto: ${projectName}`)); try { // Crear directorio principal fs.mkdirSync(projectPath, { recursive: true }); // Copiar la plantilla base await copyTemplateFiles(projectPath); console.log(chalk.green('√ Archivos de plantilla copiados')); // Asegurar compatibilidad con Pages Router await ensurePagesRouterOnly(projectPath); console.log(chalk.green('√ Estructura de carpetas creada')); // Configurar el proyecto según las opciones seleccionadas await customizeProject(projectPath, options); console.log(chalk.green('√ Compatibilidad con Pages Router configurada correctamente')); // Remover archivos no utilizados await removeUnusedFiles(projectPath, options); console.log(chalk.green('√ Proyecto personalizado según tus preferencias')); // Instalar dependencias await installDependencies(projectPath, options); // Generar archivo README personalizado await generateReadme(projectPath, options); return true; } catch (error) { console.log(chalk.red('× Error al instalar las dependencias')); console.log(chalk.red('× Error al crear la estructura de carpetas')); console.error(chalk.red(`Error: ${error.message}`)); // Limpiar directorio si algo falló try { if (fs.existsSync(projectPath)) { await fs.remove(projectPath); } } catch (cleanupError) { console.error(chalk.yellow('Advertencia: No se pudo limpiar el directorio del proyecto')); } process.exit(1); } } async function ensurePagesRouterOnly(projectPath) { const spinner = ora('Configurando compatibilidad con Pages Router...').start(); try { // Solo verificar que el archivo existe, no procesarlo const nextConfigPath = path.join(projectPath, 'next.config.js'); if (!fs.existsSync(nextConfigPath)) { spinner.fail('Error: next.config.js no encontrado'); throw new Error('Archivo de configuración Next.js no encontrado'); } spinner.succeed('Compatibilidad con Pages Router configurada correctamente'); } catch (error) { spinner.fail('Error al configurar compatibilidad con Pages Router'); throw error; } } async function copyTemplateFiles(targetPath) { const templatePath = path.resolve(__dirname, 'template'); const spinner = ora('Copiando archivos de la plantilla base...').start(); try { // Copiar todos los archivos del template al directorio destino await fs.copy(templatePath, targetPath); spinner.succeed('Archivos de plantilla copiados'); } catch (error) { spinner.fail('Error al copiar los archivos de la plantilla'); throw error; } } async function customizeProject(projectPath, options) { const { typescript, tailwindcss, eslint, database, auth, authProviders, roles, middleware, ai, aiModels, cloudinary, blog, mdxFeatures, forms, darkMode, uiComponents, framerMotion, notifications, examplePages, exampleTypes, envExample, documentation } = options; const spinner = ora('Personalizando el proyecto según tus preferencias...').start(); try { // Actualizar package.json con las dependencias necesarias const packageJsonPath = path.join(projectPath, 'package.json'); const packageJson = await fs.readJson(packageJsonPath); // Dependencias base que siempre se incluyen const dependencies = { "next": "^14.0.4", "react": "^18.2.0", "react-dom": "^18.2.0" }; const devDependencies = {}; // TypeScript (opcional) if (typescript) { devDependencies["typescript"] = "^5.3.3"; devDependencies["@types/react"] = "^18.2.45"; devDependencies["@types/node"] = "^20.10.5"; devDependencies["@types/react-dom"] = "^18.2.18"; } // TailwindCSS (opcional) if (tailwindcss) { dependencies["tailwindcss"] = "^3.4.0"; dependencies["postcss"] = "^8.4.32"; dependencies["autoprefixer"] = "^10.4.16"; dependencies["clsx"] = "^2.0.0"; dependencies["tailwind-merge"] = "^2.2.0"; } // ESLint (opcional) if (eslint) { devDependencies["eslint"] = "^8.56.0"; devDependencies["eslint-config-next"] = "^14.0.4"; devDependencies["@typescript-eslint/parser"] = "^6.15.0"; devDependencies["@typescript-eslint/eslint-plugin"] = "^6.15.0"; } // Base de datos if (database === 'MongoDB') { dependencies["mongodb"] = "^6.3.0"; dependencies["mongoose"] = "^8.0.3"; } else if (database === 'Supabase') { dependencies["@supabase/supabase-js"] = "^2.39.1"; } else if (database === 'Firebase') { dependencies["firebase"] = "^10.7.1"; dependencies["firebase-admin"] = "^11.11.1"; } // Autenticación if (auth) { dependencies["next-auth"] = "^4.24.5"; // Si no está TypeScript habilitado, añadir tipos como dependencia normal if (!typescript) { dependencies["@types/next-auth"] = "^3.15.0"; } } // Cloudinary if (cloudinary) { dependencies["cloudinary"] = "^1.41.1"; dependencies["next-cloudinary"] = "^5.13.0"; dependencies["formidable"] = "^2.1.2"; devDependencies["@types/formidable"] = "^3.4.5"; } // Modelos de IA if (ai && aiModels && aiModels.length > 0) { // Añadir cada modelo según selección if (aiModels.includes('GPT-4o (OpenAI)')) { dependencies["openai"] = "^4.24.1"; } if (aiModels.includes('Claude 3.5 (Anthropic)')) { dependencies["@anthropic-ai/sdk"] = "^0.12.0"; } if (aiModels.includes('Gemini Flash Pro (Google)')) { dependencies["@google/generative-ai"] = "^0.2.0"; } // DeepSeek usa el SDK de OpenAI if (aiModels.includes('DeepSeek V3 Chat') || aiModels.includes('DeepSeek R1 Reasoner')) { if (!dependencies["openai"]) { dependencies["openai"] = "^4.24.1"; } } } // Blog con MDX if (blog) { dependencies["gray-matter"] = "^4.0.3"; dependencies["next-mdx-remote"] = "^4.4.1"; dependencies["date-fns"] = "^2.30.0"; dependencies["remark"] = "^15.0.1"; dependencies["remark-html"] = "^16.0.1"; if (mdxFeatures) { dependencies["rehype-highlight"] = "^7.0.0"; dependencies["rehype-slug"] = "^6.0.0"; dependencies["remark-gfm"] = "^4.0.0"; } if (typescript) { devDependencies["@types/mdx"] = "^2.0.10"; } } // Formularios if (forms) { dependencies["react-hook-form"] = "^7.49.2"; dependencies["zod"] = "^3.22.4"; dependencies["@hookform/resolvers"] = "^3.3.3"; } // Tema claro/oscuro if (darkMode) { dependencies["next-themes"] = "^0.2.1"; } // Animaciones if (framerMotion) { dependencies["framer-motion"] = "^10.16.16"; } // Notificaciones if (notifications) { dependencies["react-hot-toast"] = "^2.4.1"; } // Actualizar package.json packageJson.dependencies = { ...packageJson.dependencies, ...dependencies }; packageJson.devDependencies = { ...packageJson.devDependencies, ...devDependencies }; await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 }); // Remover archivos/componentes según las opciones await removeUnusedFiles(projectPath, options); spinner.succeed('Proyecto personalizado según tus preferencias'); } catch (error) { spinner.fail('Error al personalizar el proyecto'); throw error; } } async function removeUnusedFiles(projectPath, options) { const { typescript, tailwindcss, eslint, auth, ai, cloudinary, blog, forms, darkMode, framerMotion, notifications, examplePages, exampleTypes } = options; try { // Remover archivos de TypeScript si no está habilitado if (!typescript) { const tsFiles = [ 'tsconfig.json', 'next-env.d.ts' ]; for (const file of tsFiles) { const filePath = path.join(projectPath, file); if (fs.existsSync(filePath)) { await fs.remove(filePath); } } } // Remover configuración de TailwindCSS si no está habilitado if (!tailwindcss) { const tailwindFiles = [ 'tailwind.config.js', 'postcss.config.js' ]; for (const file of tailwindFiles) { const filePath = path.join(projectPath, file); if (fs.existsSync(filePath)) { await fs.remove(filePath); } } } // Remover ESLint si no está habilitado if (!eslint) { const eslintPath = path.join(projectPath, '.eslintrc.json'); if (fs.existsSync(eslintPath)) { await fs.remove(eslintPath); } } // Remover componentes de autenticación si no está habilitado if (!auth) { const authFiles = [ 'components/auth', 'lib/auth.ts', 'lib/roles.ts', 'pages/api/auth', 'middleware.ts' ]; for (const file of authFiles) { const filePath = path.join(projectPath, file); if (fs.existsSync(filePath)) { await fs.remove(filePath); } } } // Remover componentes de IA si no está habilitado if (!ai) { const aiFiles = [ 'components/ai', 'pages/api/ai.ts', 'pages/api/openai.ts' ]; for (const file of aiFiles) { const filePath = path.join(projectPath, file); if (fs.existsSync(filePath)) { await fs.remove(filePath); } } } // Remover Cloudinary si no está habilitado if (!cloudinary) { const cloudinaryFiles = [ 'components/upload', 'pages/api/upload.ts' ]; for (const file of cloudinaryFiles) { const filePath = path.join(projectPath, file); if (fs.existsSync(filePath)) { await fs.remove(filePath); } } } // Remover blog si no está habilitado if (!blog) { const blogFiles = [ 'pages/blog', 'lib/mdx.ts', 'content' ]; for (const file of blogFiles) { const filePath = path.join(projectPath, file); if (fs.existsSync(filePath)) { await fs.remove(filePath); } } } // Remover páginas de ejemplo si no están habilitadas if (!examplePages) { const examplePath = path.join(projectPath, 'pages/ejemplos'); if (fs.existsSync(examplePath)) { await fs.remove(examplePath); } } else if (exampleTypes && exampleTypes.length > 0) { // Remover ejemplos específicos no seleccionados const examplePath = path.join(projectPath, 'pages/ejemplos'); if (!exampleTypes.includes('Ejemplo de Autenticación')) { const authExamplePath = path.join(examplePath, 'auth.tsx'); if (fs.existsSync(authExamplePath)) { await fs.remove(authExamplePath); } } if (!exampleTypes.includes('Ejemplo de IA (Multi-modelo)')) { const aiExamplePath = path.join(examplePath, 'ai.tsx'); if (fs.existsSync(aiExamplePath)) { await fs.remove(aiExamplePath); } } if (!exampleTypes.includes('Biblioteca de Componentes')) { const componentsExamplePath = path.join(examplePath, 'componentes.tsx'); if (fs.existsSync(componentsExamplePath)) { await fs.remove(componentsExamplePath); } } if (!exampleTypes.includes('Ejemplo de Carga de Imágenes')) { const uploadExamplePath = path.join(examplePath, 'upload.tsx'); if (fs.existsSync(uploadExamplePath)) { await fs.remove(uploadExamplePath); } } if (!exampleTypes.includes('Ejemplo de Formularios')) { const formsExamplePath = path.join(examplePath, 'formularios.tsx'); if (fs.existsSync(formsExamplePath)) { await fs.remove(formsExamplePath); } } if (!exampleTypes.includes('Ejemplo de Animaciones')) { const animationsExamplePath = path.join(examplePath, 'animaciones.tsx'); if (fs.existsSync(animationsExamplePath)) { await fs.remove(animationsExamplePath); } } if (!exampleTypes.includes('Ejemplo de Notificaciones')) { const notificationsExamplePath = path.join(examplePath, 'notificaciones.tsx'); if (fs.existsSync(notificationsExamplePath)) { await fs.remove(notificationsExamplePath); } } if (!exampleTypes.includes('Ejemplo de Base de Datos')) { const databaseExamplePath = path.join(examplePath, 'database.tsx'); if (fs.existsSync(databaseExamplePath)) { await fs.remove(databaseExamplePath); } // También remover las APIs de prueba de database const databaseApiPath = path.join(projectPath, 'pages/api/database'); if (fs.existsSync(databaseApiPath)) { await fs.remove(databaseApiPath); } } if (!exampleTypes.includes('Ejemplo de UI y Temas')) { const uiExamplePath = path.join(examplePath, 'ui-temas.tsx'); if (fs.existsSync(uiExamplePath)) { await fs.remove(uiExamplePath); } } if (!exampleTypes.includes('Ejemplo de TypeScript')) { const tsExamplePath = path.join(examplePath, 'typescript.tsx'); if (fs.existsSync(tsExamplePath)) { await fs.remove(tsExamplePath); } } if (!exampleTypes.includes('Páginas 404 personalizada')) { const notFoundPath = path.join(projectPath, 'pages/404.tsx'); if (fs.existsSync(notFoundPath)) { await fs.remove(notFoundPath); } } } } catch (error) { console.log(chalk.yellow('Advertencia: Error al remover algunos archivos no utilizados')); } } async function installDependencies(projectPath, options) { const spinner = ora('Instalando dependencias (esto puede tardar unos minutos)...').start(); try { // Configuración específica para Windows const isWindows = process.platform === 'win32'; const command = isWindows ? 'npm.cmd install' : 'npm install'; // Ejecutar npm install en el directorio del proyecto execSync(command, { cwd: projectPath, stdio: 'inherit', shell: isWindows }); spinner.succeed('Dependencias instaladas correctamente'); } catch (error) { spinner.fail('Error al instalar las dependencias'); console.error(chalk.red(`Error: ${error.message}`)); throw error; } } async function generateReadme(projectPath, options) { const spinner = ora('Generando README personalizado...').start(); try { const readmePath = path.join(projectPath, 'README.md'); let readmeContent = `# ${options.projectName}\n\n`; readmeContent += `Proyecto Next.js 14+ con Pages Router y varias características modernas.\n\n`; readmeContent += `## Características\n\n`; // Listar características según opciones elegidas readmeContent += `- Next.js 14+ con Pages Router\n`; readmeContent += `- TypeScript\n`; readmeContent += `- TailwindCSS\n`; if (options.darkMode) { readmeContent += `- Modo claro/oscuro con next-themes\n`; } if (options.database !== 'Ninguna') { readmeContent += `- Integración con ${options.database}\n`; } if (options.auth) { readmeContent += `- Autenticación con NextAuth.js (`; readmeContent += options.authProviders.join(', '); readmeContent += `)\n`; if (options.roles) { readmeContent += `- Sistema de roles básico (admin/user)\n`; } } if (options.blog) { readmeContent += `- Blog con MDX\n`; } if (options.forms) { readmeContent += `- Formularios con react-hook-form y validaciones zod\n`; } if (options.cloudinary) { readmeContent += `- Integración con Cloudinary para gestión de imágenes\n`; } if (options.ai) { readmeContent += `- Integración con IA\n`; } if (options.framerMotion) { readmeContent += `- Animaciones con Framer Motion\n`; } readmeContent += `- Notificaciones con react-hot-toast\n\n`; readmeContent += `## Primeros pasos\n\n`; readmeContent += `\`\`\`bash\n`; readmeContent += `npm run dev\n`; readmeContent += `\`\`\`\n\n`; readmeContent += `Abre [http://localhost:3000](http://localhost:3000) en tu navegador para ver el resultado.\n\n`; readmeContent += `## Variables de entorno\n\n`; readmeContent += `Copia el archivo \`.env.example\` a \`.env.local\` y completa las variables necesarias.\n\n`; await fs.writeFile(readmePath, readmeContent); spinner.succeed('README generado correctamente'); } catch (error) { spinner.fail('Error al generar el README'); throw error; } } module.exports = { createProject };