novatec-cli 1.0.0

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.
@@ -0,0 +1,81 @@
1
+ import path from 'path';
2
+ import chalk from 'chalk';
3
+ import fse from 'fs-extra';
4
+ import { run } from '../utils.js';
5
+
6
+ // Comandos de scaffolding por framework
7
+ const SCAFFOLD = {
8
+ react: 'npm create vite@latest frontend -- --template react',
9
+ vue: 'npm create vite@latest frontend -- --template vue',
10
+ solid: 'npm create vite@latest frontend -- --template solid',
11
+ svelte: 'npm create svelte@latest frontend -- --no-install',
12
+ astro: 'npm create astro@latest frontend -- --template minimal --no-install --no-git',
13
+ next: 'npx create-next-app@latest frontend --yes --no-git',
14
+ nuxt: 'npx nuxi@latest init frontend --no-install',
15
+ remix: 'npx create-remix@latest frontend --no-install --no-git',
16
+ angular: 'npx @angular/cli@latest new frontend --routing --style=css --skip-git --skip-install',
17
+ qwik: 'npm create qwik@latest frontend -- --no-install',
18
+ };
19
+
20
+ async function generateFrontend(config, projectPath) {
21
+ const cmd = SCAFFOLD[config.frontend];
22
+ if (!cmd) throw new Error(`Frontend no soportado: ${config.frontend}`);
23
+
24
+ console.log(chalk.blue(`\n⚛ Generando frontend (${chalk.bold(config.frontend)})...`));
25
+ run(cmd, projectPath, `Scaffolding ${config.frontend}`);
26
+
27
+ const frontendPath = path.join(projectPath, 'frontend');
28
+
29
+ // Instalar dependencias base
30
+ run('npm install', frontendPath, 'Instalando dependencias base del frontend');
31
+
32
+ // Separar tailwind del resto (va como devDependency)
33
+ const tailwindPkgs = ['tailwindcss', '@astrojs/tailwind', '@nuxtjs/tailwindcss'];
34
+ const devDeps = (config.frontendDeps || []).filter(d => tailwindPkgs.includes(d));
35
+ const prodDeps = (config.frontendDeps || []).filter(d => !tailwindPkgs.includes(d) && d !== '@shadcn/ui');
36
+
37
+ if (prodDeps.length) {
38
+ run(`npm install ${prodDeps.join(' ')}`, frontendPath, `Instalando: ${prodDeps.join(', ')}`);
39
+ }
40
+ if (devDeps.length) {
41
+ run(`npm install -D ${devDeps.join(' ')}`, frontendPath, `Instalando dev: ${devDeps.join(', ')}`);
42
+ if (devDeps.some(d => tailwindPkgs.includes(d))) {
43
+ await setupTailwind(config.frontend, frontendPath);
44
+ }
45
+ }
46
+ }
47
+
48
+ async function setupTailwind(framework, frontendPath) {
49
+ // Astro y Nuxt tienen su propia integración, no necesitan init manual
50
+ if (['astro', 'nuxt'].includes(framework)) return;
51
+
52
+ try {
53
+ run('npx tailwindcss init -p', frontendPath, 'Configurando Tailwind CSS');
54
+ } catch { /* puede fallar si ya existe */ }
55
+
56
+ const tailwindConfig = `/** @type {import('tailwindcss').Config} */
57
+ module.exports = {
58
+ content: ['./index.html', './src/**/*.{js,ts,jsx,tsx,vue,svelte}'],
59
+ theme: { extend: {} },
60
+ plugins: [],
61
+ };
62
+ `;
63
+ await fse.writeFile(path.join(frontendPath, 'tailwind.config.js'), tailwindConfig);
64
+
65
+ const cssPath = path.join(frontendPath, 'src', 'index.css');
66
+ const directives = `@tailwind base;
67
+ @tailwind components;
68
+ @tailwind utilities;
69
+
70
+ `;
71
+ if (await fse.pathExists(cssPath)) {
72
+ const existing = await fse.readFile(cssPath, 'utf8');
73
+ await fse.writeFile(cssPath, directives + existing);
74
+ } else {
75
+ await fse.ensureDir(path.join(frontendPath, 'src'));
76
+ await fse.writeFile(cssPath, directives);
77
+ }
78
+ console.log(chalk.green('✔ Tailwind CSS configurado'));
79
+ }
80
+
81
+ export { generateFrontend };
@@ -0,0 +1,99 @@
1
+ import path from 'path';
2
+ import fse from 'fs-extra';
3
+
4
+ const BADGE = {
5
+ react: '![React](https://img.shields.io/badge/React-20232A?style=flat&logo=react)',
6
+ next: '![Next.js](https://img.shields.io/badge/Next.js-000?style=flat&logo=nextdotjs)',
7
+ vue: '![Vue](https://img.shields.io/badge/Vue.js-35495E?style=flat&logo=vuedotjs)',
8
+ nuxt: '![Nuxt](https://img.shields.io/badge/Nuxt-002E3B?style=flat&logo=nuxtdotjs)',
9
+ astro: '![Astro](https://img.shields.io/badge/Astro-BC52EE?style=flat&logo=astro)',
10
+ svelte: '![Svelte](https://img.shields.io/badge/SvelteKit-FF3E00?style=flat&logo=svelte)',
11
+ solid: '![SolidJS](https://img.shields.io/badge/SolidJS-2C4F7C?style=flat&logo=solid)',
12
+ angular: '![Angular](https://img.shields.io/badge/Angular-DD0031?style=flat&logo=angular)',
13
+ remix: '![Remix](https://img.shields.io/badge/Remix-000?style=flat&logo=remix)',
14
+ qwik: '![Qwik](https://img.shields.io/badge/Qwik-AC7EF4?style=flat&logo=qwik)',
15
+ express: '![Express](https://img.shields.io/badge/Express-000?style=flat&logo=express)',
16
+ nestjs: '![NestJS](https://img.shields.io/badge/NestJS-E0234E?style=flat&logo=nestjs)',
17
+ fastify: '![Fastify](https://img.shields.io/badge/Fastify-000?style=flat&logo=fastify)',
18
+ hono: '![Hono](https://img.shields.io/badge/Hono-E36002?style=flat&logo=hono)',
19
+ fastapi: '![FastAPI](https://img.shields.io/badge/FastAPI-009688?style=flat&logo=fastapi)',
20
+ django: '![Django](https://img.shields.io/badge/Django-092E20?style=flat&logo=django)',
21
+ flask: '![Flask](https://img.shields.io/badge/Flask-000?style=flat&logo=flask)',
22
+ spring: '![Spring](https://img.shields.io/badge/Spring_Boot-6DB33F?style=flat&logo=springboot)',
23
+ deno: '![Deno](https://img.shields.io/badge/Deno-000?style=flat&logo=deno)',
24
+ gin: '![Go](https://img.shields.io/badge/Go/Gin-00ADD8?style=flat&logo=go)',
25
+ };
26
+
27
+ const START_CMD = {
28
+ frontend: { react: 'npm run dev', vue: 'npm run dev', next: 'npm run dev', nuxt: 'npm run dev', astro: 'npm run dev', svelte: 'npm run dev', solid: 'npm run dev', angular: 'ng serve', remix: 'npm run dev', qwik: 'npm run dev' },
29
+ backend: { express: 'npm run dev', nestjs: 'npm run start:dev', fastify: 'npm run dev', hono: 'npm run dev', fastapi: 'uvicorn main:app --reload', django: 'python manage.py runserver', flask: 'python run.py', spring: 'mvn spring-boot:run', deno: 'deno task dev', gin: 'go run main.go' },
30
+ };
31
+
32
+ async function generateReadme(config, projectPath) {
33
+ const feBadge = BADGE[config.frontend] || config.frontend;
34
+ const beBadge = BADGE[config.backend] || config.backend;
35
+ const isPython = ['fastapi', 'django', 'flask'].includes(config.backend);
36
+
37
+ const feDeps = config.frontendDeps?.length ? config.frontendDeps.map(d => `- \`${d}\``).join('\n') : '_ninguna_';
38
+ const beDeps = config.backendDeps?.length ? config.backendDeps.map(d => `- \`${d}\``).join('\n') : '_ninguna_';
39
+
40
+ const extrasSection = config.extras?.length
41
+ ? `\n## Extras configurados\n${config.extras.map(e => `- ${e}`).join('\n')}\n`
42
+ : '';
43
+
44
+ const dockerSection = config.extras?.includes('docker')
45
+ ? `\n## Docker\n\`\`\`bash\ndocker-compose up --build\n\`\`\`\n`
46
+ : '';
47
+
48
+ const readme = `# ${config.name}
49
+
50
+ > Proyecto full stack generado con **fullstack-cli** 🚀
51
+
52
+ ${feBadge} ${beBadge} ![License](https://img.shields.io/badge/license-MIT-blue?style=flat)
53
+
54
+ ## Stack
55
+
56
+ | Capa | Tecnología |
57
+ |----------|-----------|
58
+ | Frontend | \`${config.frontend}\` |
59
+ | Backend | \`${config.backend}\` |
60
+
61
+ ## Estructura
62
+
63
+ \`\`\`
64
+ ${config.name}/
65
+ ├── frontend/ # ${config.frontend}
66
+ ├── backend/ # ${config.backend}
67
+ ${config.extras?.includes('docker') ? '├── docker-compose.yml\n' : ''}${config.extras?.includes('ci') ? '├── .github/workflows/ci.yml\n' : ''}└── README.md
68
+ \`\`\`
69
+
70
+ ## Inicio rápido
71
+
72
+ ### Frontend
73
+ \`\`\`bash
74
+ cd frontend
75
+ npm install
76
+ ${START_CMD.frontend[config.frontend] || 'npm run dev'}
77
+ \`\`\`
78
+
79
+ ### Backend
80
+ \`\`\`bash
81
+ cd backend
82
+ ${isPython ? 'pip install -r requirements.txt\n' : 'npm install\n'}${START_CMD.backend[config.backend] || 'npm start'}
83
+ \`\`\`
84
+
85
+ ## Dependencias del Frontend
86
+ ${feDeps}
87
+
88
+ ## Dependencias del Backend
89
+ ${beDeps}
90
+ ${extrasSection}${dockerSection}
91
+ ---
92
+ Generado con [fullstack-cli](https://github.com/tu-usuario/fullstack-cli) · ${new Date().toLocaleDateString('es-MX')}
93
+ `;
94
+
95
+ await fse.writeFile(path.join(projectPath, 'README.md'), readme);
96
+ console.log('\x1b[32m✔ README.md generado con badges\x1b[0m');
97
+ }
98
+
99
+ export { generateReadme };
package/lib/prompts.js ADDED
@@ -0,0 +1,348 @@
1
+ import inquirer from 'inquirer';
2
+ import chalk from 'chalk';
3
+
4
+ // ── FRONTEND FRAMEWORKS ───────────────────────────────────────────────────────
5
+ export const FRONTEND_CHOICES = [
6
+ { name: '⚛ React (Vite) — SPA rápida con React 18', value: 'react' },
7
+ { name: '🔺 Next.js — React SSR/SSG/App Router', value: 'next' },
8
+ { name: '💚 Vue 3 — Vite + Composition API', value: 'vue' },
9
+ { name: '🔷 Nuxt 3 — Vue con SSR/SSG', value: 'nuxt' },
10
+ { name: '🚀 Astro — MPA ultra-rápido', value: 'astro' },
11
+ { name: '🔥 SvelteKit — Svelte con SSR/SSG', value: 'svelte' },
12
+ { name: '💎 SolidJS — Reactividad sin Virtual DOM', value: 'solid' },
13
+ { name: '🅰 Angular — Framework empresarial', value: 'angular' },
14
+ { name: '🌐 Remix — Full-stack React con loaders', value: 'remix' },
15
+ { name: '⚡ Qwik — Resumability, carga instantánea', value: 'qwik' },
16
+ ];
17
+
18
+ // ── BACKEND FRAMEWORKS ────────────────────────────────────────────────────────
19
+ export const BACKEND_CHOICES = [
20
+ { name: '🟢 Express — Minimalista (Node.js)', value: 'express' },
21
+ { name: '🔵 NestJS — Arquitectura modular', value: 'nestjs' },
22
+ { name: '⚡ Fastify — El más rápido (Node)', value: 'fastify' },
23
+ { name: '🔥 Hono — Edge-ready ligero', value: 'hono' },
24
+ { name: '🐍 FastAPI — Async Python alto rend', value: 'fastapi' },
25
+ { name: '🌶 Django — Baterías incluidas', value: 'django' },
26
+ { name: '🍶 Flask — Micro-framework Python', value: 'flask' },
27
+ { name: '☕ Spring Boot — Empresarial (Java)', value: 'spring' },
28
+ { name: '🦕 Deno/Oak — Seguro (TypeScript)', value: 'deno' },
29
+ { name: '🐹 Go/Gin — Rendimiento extremo', value: 'gin' },
30
+ ];
31
+
32
+ // ── DEPENDENCIAS POR FRONTEND ─────────────────────────────────────────────────
33
+ export const FRONTEND_DEPS = {
34
+ react: [
35
+ { name: 'axios — HTTP client', value: 'axios' },
36
+ { name: 'react-router-dom — Enrutamiento SPA', value: 'react-router-dom' },
37
+ { name: 'zustand — Estado global ligero', value: 'zustand' },
38
+ { name: 'jotai — Estado atómico', value: 'jotai' },
39
+ { name: '@tanstack/react-query — Server state / fetching', value: '@tanstack/react-query' },
40
+ { name: 'tailwindcss — CSS utility-first', value: 'tailwindcss' },
41
+ { name: 'shadcn/ui — Componentes accesibles', value: '@shadcn/ui' },
42
+ { name: 'framer-motion — Animaciones fluidas', value: 'framer-motion' },
43
+ { name: 'react-hook-form — Formularios performantes', value: 'react-hook-form' },
44
+ { name: 'zod — Validación de esquemas', value: 'zod' },
45
+ { name: 'date-fns — Manejo de fechas', value: 'date-fns' },
46
+ { name: 'recharts — Gráficas y dashboards', value: 'recharts' },
47
+ { name: 'i18next — Internacionalización', value: 'i18next' },
48
+ { name: 'dotenv — Variables de entorno', value: 'dotenv' },
49
+ ],
50
+ next: [
51
+ { name: 'axios — HTTP client', value: 'axios' },
52
+ { name: 'next-auth — Autenticación completa', value: 'next-auth' },
53
+ { name: 'zustand — Estado global ligero', value: 'zustand' },
54
+ { name: 'tailwindcss — CSS utility-first (incluido)', value: 'tailwindcss' },
55
+ { name: 'prisma — ORM moderno', value: 'prisma' },
56
+ { name: '@tanstack/react-query — Server state', value: '@tanstack/react-query' },
57
+ { name: 'zod — Validación de esquemas', value: 'zod' },
58
+ { name: 'react-hook-form — Formularios performantes', value: 'react-hook-form' },
59
+ { name: 'shadcn/ui — Componentes accesibles', value: '@shadcn/ui' },
60
+ { name: 'stripe — Pagos online', value: 'stripe' },
61
+ { name: 'resend — Emails transaccionales', value: 'resend' },
62
+ { name: 'uploadthing — Subida de archivos', value: 'uploadthing' },
63
+ { name: 'date-fns — Manejo de fechas', value: 'date-fns' },
64
+ ],
65
+ vue: [
66
+ { name: 'axios — HTTP client', value: 'axios' },
67
+ { name: 'vue-router — Enrutamiento oficial', value: 'vue-router' },
68
+ { name: 'pinia — Estado global oficial', value: 'pinia' },
69
+ { name: 'tailwindcss — CSS utility-first', value: 'tailwindcss' },
70
+ { name: 'vee-validate — Validación de formularios', value: 'vee-validate' },
71
+ { name: 'zod — Validación de esquemas', value: 'zod' },
72
+ { name: 'vueuse — Composables utilitarios', value: '@vueuse/core' },
73
+ { name: 'naive-ui — Componentes UI', value: 'naive-ui' },
74
+ { name: 'date-fns — Manejo de fechas', value: 'date-fns' },
75
+ { name: 'i18n — Internacionalización', value: 'vue-i18n' },
76
+ ],
77
+ nuxt: [
78
+ { name: 'axios / ofetch — HTTP client', value: 'ofetch' },
79
+ { name: '@pinia/nuxt — Estado global', value: '@pinia/nuxt' },
80
+ { name: '@nuxtjs/tailwindcss — Tailwind integrado', value: '@nuxtjs/tailwindcss' },
81
+ { name: '@nuxt/image — Optimización de imágenes', value: '@nuxt/image' },
82
+ { name: 'nuxt-auth-utils — Autenticación', value: 'nuxt-auth-utils' },
83
+ { name: 'zod — Validación', value: 'zod' },
84
+ { name: '@vueuse/nuxt — Composables', value: '@vueuse/nuxt' },
85
+ ],
86
+ astro: [
87
+ { name: 'tailwindcss — CSS utility-first', value: '@astrojs/tailwind' },
88
+ { name: '@astrojs/react — Integración React', value: '@astrojs/react' },
89
+ { name: '@astrojs/vue — Integración Vue', value: '@astrojs/vue' },
90
+ { name: '@astrojs/mdx — Soporte MDX', value: '@astrojs/mdx' },
91
+ { name: '@astrojs/image — Optimización imágenes', value: '@astrojs/image' },
92
+ { name: 'nanostores — Estado global ligero', value: 'nanostores' },
93
+ ],
94
+ svelte: [
95
+ { name: 'axios — HTTP client', value: 'axios' },
96
+ { name: 'tailwindcss — CSS utility-first', value: 'tailwindcss' },
97
+ { name: 'zod — Validación', value: 'zod' },
98
+ { name: 'svelte-query — Server state', value: '@tanstack/svelte-query' },
99
+ { name: 'skeleton — UI components', value: '@skeletonlabs/skeleton' },
100
+ { name: 'lucia — Autenticación', value: 'lucia' },
101
+ ],
102
+ solid: [
103
+ { name: 'axios — HTTP client', value: 'axios' },
104
+ { name: '@solidjs/router — Enrutamiento', value: '@solidjs/router' },
105
+ { name: 'tailwindcss — CSS utility-first', value: 'tailwindcss' },
106
+ { name: 'solid-query — Server state', value: '@tanstack/solid-query' },
107
+ { name: 'zod — Validación', value: 'zod' },
108
+ ],
109
+ angular: [
110
+ { name: 'axios — HTTP client', value: 'axios' },
111
+ { name: '@angular/material — Material Design UI', value: '@angular/material' },
112
+ { name: 'tailwindcss — CSS utility-first', value: 'tailwindcss' },
113
+ { name: 'ngrx/store — Estado global Redux-like', value: '@ngrx/store' },
114
+ { name: 'zod — Validación', value: 'zod' },
115
+ { name: 'date-fns — Manejo de fechas', value: 'date-fns' },
116
+ ],
117
+ remix: [
118
+ { name: 'tailwindcss — CSS utility-first', value: 'tailwindcss' },
119
+ { name: 'zod — Validación', value: 'zod' },
120
+ { name: 'prisma — ORM moderno', value: 'prisma' },
121
+ { name: 'react-hook-form — Formularios', value: 'react-hook-form' },
122
+ { name: 'date-fns — Fechas', value: 'date-fns' },
123
+ ],
124
+ qwik: [
125
+ { name: 'tailwindcss — CSS utility-first', value: 'tailwindcss' },
126
+ { name: 'zod — Validación', value: 'zod' },
127
+ { name: 'axios — HTTP client', value: 'axios' },
128
+ ],
129
+ };
130
+
131
+ // ── DEPENDENCIAS POR BACKEND ──────────────────────────────────────────────────
132
+ export const BACKEND_DEPS = {
133
+ express: [
134
+ { name: 'cors — Habilitar CORS', value: 'cors' },
135
+ { name: 'dotenv — Variables de entorno', value: 'dotenv' },
136
+ { name: 'mongoose — ODM para MongoDB', value: 'mongoose' },
137
+ { name: 'prisma — ORM moderno (SQL)', value: 'prisma' },
138
+ { name: 'jsonwebtoken — Autenticación JWT', value: 'jsonwebtoken' },
139
+ { name: 'bcryptjs — Hash de contraseñas', value: 'bcryptjs' },
140
+ { name: 'express-validator — Validación de inputs', value: 'express-validator' },
141
+ { name: 'multer — Subida de archivos', value: 'multer' },
142
+ { name: 'helmet — Seguridad HTTP headers', value: 'helmet' },
143
+ { name: 'morgan — Logger HTTP', value: 'morgan' },
144
+ { name: 'rate-limiter — Limitar peticiones', value: 'express-rate-limit' },
145
+ { name: 'swagger-ui — Documentación API', value: 'swagger-ui-express' },
146
+ { name: 'socket.io — WebSockets en tiempo real', value: 'socket.io' },
147
+ { name: 'bull — Colas de trabajo', value: 'bull' },
148
+ { name: 'nodemailer — Envío de emails', value: 'nodemailer' },
149
+ { name: 'nodemon — Auto-reload en dev', value: 'nodemon' },
150
+ ],
151
+ nestjs: [
152
+ { name: 'prisma — ORM moderno (SQL)', value: 'prisma' },
153
+ { name: '@nestjs/jwt — Módulo JWT oficial', value: '@nestjs/jwt' },
154
+ { name: '@nestjs/config — Variables de entorno', value: '@nestjs/config' },
155
+ { name: 'class-validator — Validación de DTOs', value: 'class-validator' },
156
+ { name: 'class-transformer — Transformación de objetos', value: 'class-transformer' },
157
+ { name: 'mongoose — ODM para MongoDB', value: 'mongoose' },
158
+ { name: '@nestjs/swagger — Documentación API', value: '@nestjs/swagger' },
159
+ { name: '@nestjs/websockets — WebSockets', value: '@nestjs/websockets' },
160
+ { name: '@nestjs/bull — Colas de trabajo', value: '@nestjs/bull' },
161
+ { name: '@nestjs/mailer — Envío de emails', value: '@nestjs-modules/mailer' },
162
+ { name: 'passport — Autenticación estrategias', value: '@nestjs/passport' },
163
+ ],
164
+ fastify: [
165
+ { name: '@fastify/cors — CORS', value: '@fastify/cors' },
166
+ { name: '@fastify/jwt — JWT', value: '@fastify/jwt' },
167
+ { name: '@fastify/swagger — Documentación API', value: '@fastify/swagger' },
168
+ { name: '@fastify/multipart — Subida de archivos', value: '@fastify/multipart' },
169
+ { name: '@fastify/rate-limit — Rate limiting', value: '@fastify/rate-limit' },
170
+ { name: 'prisma — ORM moderno', value: 'prisma' },
171
+ { name: 'mongoose — ODM MongoDB', value: 'mongoose' },
172
+ { name: 'dotenv — Variables de entorno', value: 'dotenv' },
173
+ { name: 'nodemon — Auto-reload', value: 'nodemon' },
174
+ ],
175
+ hono: [
176
+ { name: 'hono/jwt — JWT middleware', value: '@hono/jwt' },
177
+ { name: 'zod — Validación', value: 'zod' },
178
+ { name: 'drizzle-orm — ORM ligero', value: 'drizzle-orm' },
179
+ { name: 'prisma — ORM moderno', value: 'prisma' },
180
+ { name: 'dotenv — Variables de entorno', value: 'dotenv' },
181
+ ],
182
+ fastapi: [
183
+ { name: 'sqlalchemy — ORM para Python', value: 'sqlalchemy' },
184
+ { name: 'alembic — Migraciones de BD', value: 'alembic' },
185
+ { name: 'python-jose — JWT para Python', value: 'python-jose[cryptography]' },
186
+ { name: 'passlib — Hash de contraseñas', value: 'passlib[bcrypt]' },
187
+ { name: 'python-dotenv — Variables de entorno', value: 'python-dotenv' },
188
+ { name: 'httpx — HTTP client async', value: 'httpx' },
189
+ { name: 'celery — Colas de tareas', value: 'celery' },
190
+ { name: 'redis — Cache / broker', value: 'redis' },
191
+ { name: 'pydantic-settings — Config tipada', value: 'pydantic-settings' },
192
+ { name: 'tortoise-orm — ORM async', value: 'tortoise-orm' },
193
+ { name: 'aiofiles — Archivos async', value: 'aiofiles' },
194
+ ],
195
+ django: [
196
+ { name: 'djangorestframework — API REST', value: 'djangorestframework' },
197
+ { name: 'django-cors-headers — CORS', value: 'django-cors-headers' },
198
+ { name: 'djangorestframework-simplejwt — JWT', value: 'djangorestframework-simplejwt' },
199
+ { name: 'celery — Colas de tareas', value: 'celery' },
200
+ { name: 'redis — Cache / broker', value: 'redis' },
201
+ { name: 'pillow — Procesamiento de imágenes', value: 'Pillow' },
202
+ { name: 'python-dotenv — Variables de entorno', value: 'python-dotenv' },
203
+ { name: 'psycopg2 — PostgreSQL driver', value: 'psycopg2-binary' },
204
+ { name: 'drf-spectacular — Documentación OpenAPI', value: 'drf-spectacular' },
205
+ ],
206
+ flask: [
207
+ { name: 'flask-restful — API REST', value: 'flask-restful' },
208
+ { name: 'flask-sqlalchemy — ORM integrado', value: 'flask-sqlalchemy' },
209
+ { name: 'flask-jwt-extended — JWT', value: 'flask-jwt-extended' },
210
+ { name: 'flask-cors — CORS', value: 'flask-cors' },
211
+ { name: 'flask-migrate — Migraciones', value: 'flask-migrate' },
212
+ { name: 'marshmallow — Serialización', value: 'marshmallow' },
213
+ { name: 'python-dotenv — Variables de entorno', value: 'python-dotenv' },
214
+ { name: 'celery — Colas de tareas', value: 'celery' },
215
+ ],
216
+ spring: [
217
+ { name: 'spring-data-jpa — ORM / JPA', value: 'spring-data-jpa' },
218
+ { name: 'spring-security — Seguridad y auth', value: 'spring-security' },
219
+ { name: 'spring-web — REST controllers', value: 'spring-web' },
220
+ { name: 'lombok — Reduce boilerplate', value: 'lombok' },
221
+ { name: 'postgresql-driver — Driver PostgreSQL', value: 'postgresql' },
222
+ { name: 'h2-database — BD en memoria (dev)', value: 'h2' },
223
+ ],
224
+ deno: [
225
+ { name: 'oak/cors — CORS middleware', value: 'cors' },
226
+ { name: 'djwt — JWT para Deno', value: 'djwt' },
227
+ { name: 'zod — Validación', value: 'zod' },
228
+ ],
229
+ gin: [
230
+ { name: 'gorm — ORM para Go', value: 'gorm' },
231
+ { name: 'jwt-go — JWT', value: 'jwt-go' },
232
+ { name: 'godotenv — Variables de entorno', value: 'godotenv' },
233
+ { name: 'validator — Validación de structs', value: 'validator' },
234
+ ],
235
+ };
236
+
237
+ // ── EXTRAS GLOBALES ───────────────────────────────────────────────────────────
238
+ export const EXTRAS_CHOICES = [
239
+ { name: '🔀 Git init automático', value: 'git', checked: true },
240
+ { name: '🔍 ESLint + Prettier (linting/formato)', value: 'eslint' },
241
+ { name: '🐶 Husky + lint-staged (pre-commit hooks)', value: 'husky' },
242
+ { name: '🧪 Vitest / Jest (testing)', value: 'testing' },
243
+ { name: '🐳 docker-compose.yml', value: 'docker' },
244
+ { name: '🔒 Archivos .env.example', value: 'env' },
245
+ { name: '📋 .editorconfig', value: 'editorconfig' },
246
+ { name: '🚀 GitHub Actions CI/CD básico', value: 'ci' },
247
+ ];
248
+
249
+ // ── FLUJO PRINCIPAL ───────────────────────────────────────────────────────────
250
+ export async function askProjectOptions(nameArg, options) {
251
+ const answers = {};
252
+
253
+ if (!nameArg) {
254
+ const { name } = await inquirer.prompt([{
255
+ type: 'input',
256
+ name: 'name',
257
+ message: chalk.cyan('📦 Nombre del proyecto:'),
258
+ default: 'my-app',
259
+ validate: v => /^[a-z0-9-_]+$/i.test(v.trim()) || 'Solo letras, números, guiones y guiones bajos',
260
+ }]);
261
+ answers.name = name.trim();
262
+ } else {
263
+ answers.name = nameArg;
264
+ }
265
+
266
+ if (!options.frontend) {
267
+ const { frontend } = await inquirer.prompt([{
268
+ type: 'list',
269
+ name: 'frontend',
270
+ message: chalk.cyan('⚛ Elige el framework de Frontend:'),
271
+ choices: FRONTEND_CHOICES,
272
+ pageSize: 12,
273
+ }]);
274
+ answers.frontend = frontend;
275
+ } else {
276
+ answers.frontend = options.frontend;
277
+ }
278
+
279
+ if (!options.backend) {
280
+ const { backend } = await inquirer.prompt([{
281
+ type: 'list',
282
+ name: 'backend',
283
+ message: chalk.cyan('🔧 Elige el framework de Backend:'),
284
+ choices: BACKEND_CHOICES,
285
+ pageSize: 12,
286
+ }]);
287
+ answers.backend = backend;
288
+ } else {
289
+ answers.backend = options.backend;
290
+ }
291
+
292
+ // Dependencias contextuales frontend
293
+ console.log(chalk.gray(`\n 💡 Dependencias recomendadas para ${chalk.bold(answers.frontend)}:`));
294
+ const { frontendDeps } = await inquirer.prompt([{
295
+ type: 'checkbox',
296
+ name: 'frontendDeps',
297
+ message: chalk.cyan('📦 Dependencias para el Frontend (espacio = seleccionar):'),
298
+ choices: FRONTEND_DEPS[answers.frontend] || [],
299
+ pageSize: 14,
300
+ }]);
301
+
302
+ // Dependencias contextuales backend
303
+ console.log(chalk.gray(`\n 💡 Dependencias recomendadas para ${chalk.bold(answers.backend)}:`));
304
+ const { backendDeps } = await inquirer.prompt([{
305
+ type: 'checkbox',
306
+ name: 'backendDeps',
307
+ message: chalk.cyan('📦 Dependencias para el Backend (espacio = seleccionar):'),
308
+ choices: BACKEND_DEPS[answers.backend] || [],
309
+ pageSize: 14,
310
+ }]);
311
+
312
+ // Extras
313
+ const { extras } = await inquirer.prompt([{
314
+ type: 'checkbox',
315
+ name: 'extras',
316
+ message: chalk.cyan('⚙ Opciones adicionales del proyecto:'),
317
+ choices: EXTRAS_CHOICES,
318
+ pageSize: 10,
319
+ }]);
320
+
321
+ // Resumen + confirmación
322
+ console.log();
323
+ console.log(chalk.cyan(' ╔══════════════════════════════════════════════════════╗'));
324
+ console.log(chalk.cyan(' ║') + chalk.bold.white(' 📋 RESUMEN DEL PROYECTO ') + chalk.cyan('║'));
325
+ console.log(chalk.cyan(' ╠══════════════════════════════════════════════════════╣'));
326
+ console.log(chalk.cyan(' ║') + chalk.white(` 📦 Nombre : `) + chalk.cyan.bold(answers.name.padEnd(36)) + chalk.cyan('║'));
327
+ console.log(chalk.cyan(' ║') + chalk.white(` ⚛ Frontend : `) + chalk.cyan.bold(answers.frontend.padEnd(36)) + chalk.cyan('║'));
328
+ console.log(chalk.cyan(' ║') + chalk.white(` 🔧 Backend : `) + chalk.cyan.bold(answers.backend.padEnd(36)) + chalk.cyan('║'));
329
+ console.log(chalk.cyan(' ║') + chalk.white(` 📦 FE deps : `) + chalk.gray((frontendDeps.length ? frontendDeps.length + ' seleccionadas' : 'ninguna').padEnd(36)) + chalk.cyan('║'));
330
+ console.log(chalk.cyan(' ║') + chalk.white(` 📦 BE deps : `) + chalk.gray((backendDeps.length ? backendDeps.length + ' seleccionadas' : 'ninguna').padEnd(36)) + chalk.cyan('║'));
331
+ console.log(chalk.cyan(' ║') + chalk.white(` ⚙ Extras : `) + chalk.gray((extras.length ? extras.join(', ') : 'ninguno').substring(0, 36).padEnd(36)) + chalk.cyan('║'));
332
+ console.log(chalk.cyan(' ╚══════════════════════════════════════════════════════╝'));
333
+ console.log();
334
+
335
+ const { confirm } = await inquirer.prompt([{
336
+ type: 'confirm',
337
+ name: 'confirm',
338
+ message: chalk.yellow('¿Crear el proyecto con esta configuración?'),
339
+ default: true,
340
+ }]);
341
+
342
+ if (!confirm) {
343
+ console.log(chalk.yellow('\n Operación cancelada.\n'));
344
+ process.exit(0);
345
+ }
346
+
347
+ return { name: answers.name, frontend: answers.frontend, backend: answers.backend, frontendDeps, backendDeps, extras };
348
+ }
package/lib/utils.js ADDED
@@ -0,0 +1,40 @@
1
+ import { execSync, spawnSync } from 'child_process';
2
+ import chalk from 'chalk';
3
+ import ora from 'ora';
4
+
5
+ export function isInstalled(cmd) {
6
+ const r = spawnSync(cmd, ['--version'], { stdio: 'pipe', shell: true });
7
+ return r.status === 0;
8
+ }
9
+
10
+ export async function checkRequirements() {
11
+ const spinner = ora({ text: chalk.cyan('Verificando entorno...'), color: 'cyan' }).start();
12
+ const missing = [];
13
+ if (!isInstalled('node')) missing.push('Node.js');
14
+ if (!isInstalled('npm')) missing.push('npm');
15
+ if (missing.length) {
16
+ spinner.fail(chalk.red('Faltan herramientas: ' + missing.join(', ')));
17
+ throw new Error('Instala las herramientas faltantes y vuelve a intentarlo.');
18
+ }
19
+ const nodeVer = execSync('node --version', { encoding: 'utf8' }).trim();
20
+ const npmVer = execSync('npm --version', { encoding: 'utf8' }).trim();
21
+ spinner.succeed(
22
+ chalk.green('Entorno OK ') +
23
+ chalk.gray('│') +
24
+ chalk.cyan(` Node ${nodeVer} `) +
25
+ chalk.gray('│') +
26
+ chalk.cyan(` npm v${npmVer}`)
27
+ );
28
+ console.log();
29
+ }
30
+
31
+ export function run(cmd, cwd, label) {
32
+ const spinner = ora({ text: chalk.white(label), color: 'cyan', spinner: 'dots2' }).start();
33
+ const result = spawnSync(cmd, { cwd, shell: true, stdio: 'pipe', encoding: 'utf8' });
34
+ if (result.status !== 0) {
35
+ spinner.fail(chalk.red(`✖ ${label}`));
36
+ const msg = result.stderr || result.stdout || 'Error desconocido';
37
+ throw new Error(msg.trim());
38
+ }
39
+ spinner.succeed(chalk.green(`✔ ${label}`));
40
+ }
package/package.json ADDED
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "novatec-cli",
3
+ "version": "1.0.0",
4
+ "description": "🚀 NOVATEC FULLSTACK CLI — Generador profesional de proyectos full stack | React, Next.js, Vue, Express, NestJS, FastAPI y más",
5
+ "type": "module",
6
+ "main": "./lib/create.js",
7
+ "bin": {
8
+ "novatec-cli": "bin/index.js"
9
+ },
10
+ "scripts": {
11
+ "start": "node ./bin/index.js create test-project",
12
+ "test": "echo \"Error: no test specified\" && exit 1"
13
+ },
14
+ "keywords": [
15
+ "cli",
16
+ "fullstack",
17
+ "generator",
18
+ "scaffolder",
19
+ "react",
20
+ "nextjs",
21
+ "vue",
22
+ "nuxt",
23
+ "express",
24
+ "nestjs",
25
+ "fastapi",
26
+ "project-generator",
27
+ "boilerplate",
28
+ "full-stack-framework",
29
+ "novatec"
30
+ ],
31
+ "author": "NOVATEC",
32
+ "license": "MIT",
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "https://github.com/yourusername/novatec-fullstack-cli"
36
+ },
37
+ "bugs": {
38
+ "url": "https://github.com/yourusername/novatec-fullstack-cli/issues"
39
+ },
40
+ "homepage": "https://github.com/yourusername/novatec-fullstack-cli#readme",
41
+ "dependencies": {
42
+ "boxen": "^7.1.1",
43
+ "chalk": "^5.3.0",
44
+ "chalk-animation": "^1.6.0",
45
+ "commander": "^11.1.0",
46
+ "figlet": "^1.7.0",
47
+ "fs-extra": "^11.2.0",
48
+ "gradient-string": "^2.0.2",
49
+ "inquirer": "^8.2.6",
50
+ "ora": "^5.4.1"
51
+ },
52
+ "engines": {
53
+ "node": ">=18.0.0",
54
+ "npm": ">=9.0.0"
55
+ },
56
+ "preferGlobal": true,
57
+ "files": [
58
+ "bin/",
59
+ "lib/",
60
+ "templates/",
61
+ "README.md",
62
+ "LICENSE"
63
+ ]
64
+ }