novatec-cli 3.0.6 → 3.0.8
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/bin/index.js +37 -6
- package/lib/generators/backend.js +14 -8
- package/lib/generators/deploy.js +137 -116
- package/lib/generators/frontend.js +17 -2
- package/lib/prompts.js +66 -32
- package/package.json +1 -1
package/bin/index.js
CHANGED
|
@@ -26,8 +26,9 @@ async function checkForUpdates() {
|
|
|
26
26
|
async function showBanner() {
|
|
27
27
|
console.clear();
|
|
28
28
|
const logo = figlet.textSync('NOVATEC', { font: 'Slant' });
|
|
29
|
+
const grad = (await import('gradient-string')).default;
|
|
29
30
|
console.log();
|
|
30
|
-
logo.split('\n').filter(l => l.trim()).forEach(l => console.log(' ' +
|
|
31
|
+
logo.split('\n').filter(l => l.trim()).forEach(l => console.log(' ' + grad.cristal.multiline(l)));
|
|
31
32
|
console.log();
|
|
32
33
|
const now = new Date().toLocaleDateString('es-MX', { day: '2-digit', month: 'short', year: 'numeric' });
|
|
33
34
|
console.log(' ' + chalk.gray(`v${pkg.version} · Enterprise Fullstack CLI · ${now}`));
|
|
@@ -36,10 +37,40 @@ async function showBanner() {
|
|
|
36
37
|
console.log();
|
|
37
38
|
console.log(boxen(
|
|
38
39
|
chalk.bold.white(' STACK DISPONIBLE\n\n') +
|
|
39
|
-
chalk.white.bold(' Frontend ') + chalk.gray(' │ ') +
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
chalk.white.bold(' Frontend ') + chalk.gray(' │ ') +
|
|
41
|
+
[
|
|
42
|
+
grad('#61DAFB','#21D4FD')(' React'),
|
|
43
|
+
grad('#fff','#888')(' Next.js'),
|
|
44
|
+
grad('#42b883','#35495e')(' Vue'),
|
|
45
|
+
grad('#00DC82','#003543')(' Nuxt'),
|
|
46
|
+
grad('#FF5D01','#BC52EE')(' Astro'),
|
|
47
|
+
grad('#FF3E00','#FF8C00')(' Svelte'),
|
|
48
|
+
grad('#2C4F7C','#446B9E')(' Solid'),
|
|
49
|
+
grad('#DD0031','#C3002F')(' Angular'),
|
|
50
|
+
grad('#121212','#818CF8')(' Remix'),
|
|
51
|
+
grad('#18B6F6','#AC7EF4')(' Qwik'),
|
|
52
|
+
].join(chalk.gray(' · ')) + '\n\n' +
|
|
53
|
+
chalk.white.bold(' Backend ') + chalk.gray(' │ ') +
|
|
54
|
+
[
|
|
55
|
+
grad('#fff','#aaa')(' Express'),
|
|
56
|
+
grad('#E0234E','#FF6B6B')(' NestJS'),
|
|
57
|
+
grad('#000','#888')(' Fastify'),
|
|
58
|
+
grad('#FF5733','#FF8C00')(' Hono'),
|
|
59
|
+
grad('#009688','#26A69A')(' FastAPI'),
|
|
60
|
+
grad('#092E20','#44B78B')(' Django'),
|
|
61
|
+
grad('#fff','#aaa')(' Flask'),
|
|
62
|
+
grad('#6DB33F','#77BC1F')(' Spring'),
|
|
63
|
+
grad('#fff','#aaa')(' Deno'),
|
|
64
|
+
grad('#00ACD7','#5DC9E2')(' Go/Gin'),
|
|
65
|
+
].join(chalk.gray(' · ')) + '\n\n' +
|
|
66
|
+
chalk.white.bold(' Database ') + chalk.gray(' │ ') +
|
|
67
|
+
[
|
|
68
|
+
grad('#336791','#4A90D9')(' PostgreSQL'),
|
|
69
|
+
grad('#4479A1','#00758F')(' MySQL'),
|
|
70
|
+
grad('#47A248','#00ED64')(' MongoDB'),
|
|
71
|
+
grad('#003B57','#0F80CC')(' SQLite'),
|
|
72
|
+
grad('#3ECF8E','#1C1C1C')(' Supabase'),
|
|
73
|
+
].join(chalk.gray(' · ')) + '\n\n' +
|
|
43
74
|
chalk.white.bold(' Comandos ') + chalk.gray(' │ ') +
|
|
44
75
|
[chalk.white('create'), chalk.white('add'), chalk.white('doctor'), chalk.white('build'), chalk.white('deploy'), chalk.white('list'), chalk.white('update')]
|
|
45
76
|
.join(chalk.gray(' · ')),
|
|
@@ -48,7 +79,7 @@ async function showBanner() {
|
|
|
48
79
|
console.log();
|
|
49
80
|
console.log(boxen(
|
|
50
81
|
chalk.gray(' $ novatec create my-app\n') +
|
|
51
|
-
chalk.gray(' $ novatec create my-app --
|
|
82
|
+
chalk.gray(' $ novatec create my-app --frontend next --backend express --db postgres\n') +
|
|
52
83
|
chalk.gray(' $ novatec add crud Product\n') +
|
|
53
84
|
chalk.gray(' $ novatec doctor'),
|
|
54
85
|
{ padding: { top: 0, bottom: 0, left: 1, right: 4 }, margin: { left: 2 }, borderStyle: 'single', borderColor: 'gray', dimBorder: true }
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
2
|
import chalk from 'chalk';
|
|
3
3
|
import fse from 'fs-extra';
|
|
4
|
+
import gradient from 'gradient-string';
|
|
4
5
|
import { execSync } from 'child_process';
|
|
5
6
|
import { isInstalled } from '../utils.js';
|
|
6
7
|
|
|
7
|
-
// DB packages por backend type
|
|
8
8
|
const NODE_DB_DEPS = {
|
|
9
9
|
postgres: ['pg', 'pg-hstore'],
|
|
10
10
|
mysql: ['mysql2'],
|
|
@@ -14,13 +14,17 @@ const NODE_DB_DEPS = {
|
|
|
14
14
|
none: [],
|
|
15
15
|
};
|
|
16
16
|
|
|
17
|
+
const COLORS = {
|
|
18
|
+
express: ['#ffffff', '#aaaaaa'], nestjs: ['#E0234E', '#FF6B6B'],
|
|
19
|
+
fastify: ['#000000', '#888888'], hono: ['#FF5733', '#FF8C00'],
|
|
20
|
+
fastapi: ['#009688', '#26A69A'], django: ['#092E20', '#44B78B'],
|
|
21
|
+
flask: ['#ffffff', '#aaaaaa'], spring: ['#6DB33F', '#77BC1F'],
|
|
22
|
+
deno: ['#ffffff', '#aaaaaa'], gin: ['#00ACD7', '#5DC9E2'],
|
|
23
|
+
};
|
|
24
|
+
|
|
17
25
|
function installNodeDeps(prod, dev, cwd) {
|
|
18
|
-
if (prod.length) {
|
|
19
|
-
|
|
20
|
-
}
|
|
21
|
-
if (dev && dev.length) {
|
|
22
|
-
execSync(`npm install -D ${dev.join(' ')}`, { cwd, stdio: 'pipe' });
|
|
23
|
-
}
|
|
26
|
+
if (prod.length) execSync(`npm install ${prod.join(' ')}`, { cwd, stdio: 'pipe' });
|
|
27
|
+
if (dev && dev.length) execSync(`npm install -D ${dev.join(' ')}`, { cwd, stdio: 'pipe' });
|
|
24
28
|
}
|
|
25
29
|
|
|
26
30
|
function backendPath(projectPath) {
|
|
@@ -28,7 +32,9 @@ function backendPath(projectPath) {
|
|
|
28
32
|
}
|
|
29
33
|
|
|
30
34
|
export async function generateBackend(config, projectPath) {
|
|
31
|
-
|
|
35
|
+
const [c1, c2] = COLORS[config.backend] || ['#ffffff', '#aaaaaa'];
|
|
36
|
+
const label = gradient(c1, c2)(` ◆ ${config.backend.toUpperCase()} `);
|
|
37
|
+
console.log(`\n ${label} ${chalk.gray('Generando backend...')}`);
|
|
32
38
|
const handlers = { express, nestjs, fastify, hono, fastapi, django, flask, spring, deno, gin };
|
|
33
39
|
const fn = handlers[config.backend];
|
|
34
40
|
if (!fn) throw new Error(`Backend no soportado: ${config.backend}`);
|
package/lib/generators/deploy.js
CHANGED
|
@@ -2,101 +2,9 @@ import path from 'path';
|
|
|
2
2
|
import fse from 'fs-extra';
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import boxen from 'boxen';
|
|
5
|
-
import { execSync } from 'child_process';
|
|
5
|
+
import { execSync, spawnSync } from 'child_process';
|
|
6
6
|
import { isInstalled } from '../utils.js';
|
|
7
7
|
|
|
8
|
-
// ── GitHub Actions CI ─────────────────────────────────────────────────────────
|
|
9
|
-
export async function generateDeploy(config, root) {
|
|
10
|
-
const ciDir = path.join(root, '.github', 'workflows');
|
|
11
|
-
await fse.ensureDir(ciDir);
|
|
12
|
-
const isPython = ['fastapi','django','flask'].includes(config.backend);
|
|
13
|
-
|
|
14
|
-
await fse.writeFile(path.join(ciDir, 'ci.yml'),
|
|
15
|
-
`name: CI/CD Pipeline
|
|
16
|
-
|
|
17
|
-
on:
|
|
18
|
-
push:
|
|
19
|
-
branches: [main, develop]
|
|
20
|
-
pull_request:
|
|
21
|
-
branches: [main]
|
|
22
|
-
|
|
23
|
-
jobs:
|
|
24
|
-
frontend:
|
|
25
|
-
name: Frontend — Build & Test
|
|
26
|
-
runs-on: ubuntu-latest
|
|
27
|
-
defaults:
|
|
28
|
-
run:
|
|
29
|
-
working-directory: frontend
|
|
30
|
-
steps:
|
|
31
|
-
- uses: actions/checkout@v4
|
|
32
|
-
- uses: actions/setup-node@v4
|
|
33
|
-
with:
|
|
34
|
-
node-version: 20
|
|
35
|
-
cache: npm
|
|
36
|
-
cache-dependency-path: frontend/package-lock.json
|
|
37
|
-
- run: npm ci
|
|
38
|
-
- run: npm run lint --if-present
|
|
39
|
-
- run: npm test --if-present
|
|
40
|
-
- run: npm run build
|
|
41
|
-
|
|
42
|
-
backend:
|
|
43
|
-
name: Backend — Lint & Test
|
|
44
|
-
runs-on: ubuntu-latest
|
|
45
|
-
defaults:
|
|
46
|
-
run:
|
|
47
|
-
working-directory: backend
|
|
48
|
-
steps:
|
|
49
|
-
- uses: actions/checkout@v4
|
|
50
|
-
${isPython
|
|
51
|
-
? ` - uses: actions/setup-python@v5
|
|
52
|
-
with:
|
|
53
|
-
python-version: '3.11'
|
|
54
|
-
- run: pip install -r requirements.txt
|
|
55
|
-
- run: python -m pytest --if-present`
|
|
56
|
-
: ` - uses: actions/setup-node@v4
|
|
57
|
-
with:
|
|
58
|
-
node-version: 20
|
|
59
|
-
- run: npm ci
|
|
60
|
-
- run: npm run lint --if-present
|
|
61
|
-
- run: npm test --if-present`}
|
|
62
|
-
`);
|
|
63
|
-
|
|
64
|
-
await fse.writeFile(path.join(ciDir, 'deploy.yml'),
|
|
65
|
-
`name: Deploy
|
|
66
|
-
|
|
67
|
-
on:
|
|
68
|
-
push:
|
|
69
|
-
branches: [main]
|
|
70
|
-
|
|
71
|
-
jobs:
|
|
72
|
-
deploy-frontend:
|
|
73
|
-
name: Deploy Frontend → Vercel
|
|
74
|
-
runs-on: ubuntu-latest
|
|
75
|
-
steps:
|
|
76
|
-
- uses: actions/checkout@v4
|
|
77
|
-
- uses: actions/setup-node@v4
|
|
78
|
-
with:
|
|
79
|
-
node-version: 20
|
|
80
|
-
- run: npm install -g vercel
|
|
81
|
-
- run: cd frontend && npm ci && npm run build
|
|
82
|
-
- run: vercel --prod --token \${{ secrets.VERCEL_TOKEN }} --yes
|
|
83
|
-
env:
|
|
84
|
-
VERCEL_ORG_ID: \${{ secrets.VERCEL_ORG_ID }}
|
|
85
|
-
VERCEL_PROJECT_ID: \${{ secrets.VERCEL_PROJECT_ID }}
|
|
86
|
-
|
|
87
|
-
deploy-backend:
|
|
88
|
-
name: Deploy Backend → Railway
|
|
89
|
-
runs-on: ubuntu-latest
|
|
90
|
-
needs: deploy-frontend
|
|
91
|
-
steps:
|
|
92
|
-
- uses: actions/checkout@v4
|
|
93
|
-
- uses: railwayapp/railway-github-action@v1
|
|
94
|
-
with:
|
|
95
|
-
railway-token: \${{ secrets.RAILWAY_TOKEN }}
|
|
96
|
-
service: backend
|
|
97
|
-
`);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
8
|
// ── novatec build ─────────────────────────────────────────────────────────────
|
|
101
9
|
export async function runBuild(options) {
|
|
102
10
|
const root = process.cwd();
|
|
@@ -107,12 +15,13 @@ export async function runBuild(options) {
|
|
|
107
15
|
console.log(chalk.white(' Building proyecto...\n'));
|
|
108
16
|
|
|
109
17
|
if ((options.frontend || !options.backend) && hasFe) {
|
|
110
|
-
const
|
|
18
|
+
const ora = (await import('ora')).default;
|
|
19
|
+
const spinner = ora({ text: chalk.white('Build frontend...'), spinner: 'dots', color: 'white' }).start();
|
|
111
20
|
try {
|
|
112
21
|
execSync('npm run build', { cwd: path.join(root, 'frontend'), stdio: 'pipe', encoding: 'utf8' });
|
|
113
|
-
spinner.succeed(chalk.white('Frontend build
|
|
22
|
+
spinner.succeed(chalk.white('Frontend build ✔'));
|
|
114
23
|
} catch (err) {
|
|
115
|
-
spinner.fail(chalk.red('Frontend build fall
|
|
24
|
+
spinner.fail(chalk.red('Frontend build falló: ' + (err.stderr || err.message || '').split('\n')[0]));
|
|
116
25
|
}
|
|
117
26
|
}
|
|
118
27
|
|
|
@@ -121,13 +30,16 @@ export async function runBuild(options) {
|
|
|
121
30
|
if (await fse.pathExists(pkgPath)) {
|
|
122
31
|
const pkg = await fse.readJSON(pkgPath);
|
|
123
32
|
if (pkg.scripts?.build) {
|
|
124
|
-
const
|
|
33
|
+
const ora = (await import('ora')).default;
|
|
34
|
+
const spinner = ora({ text: chalk.white('Build backend...'), spinner: 'dots', color: 'white' }).start();
|
|
125
35
|
try {
|
|
126
36
|
execSync('npm run build', { cwd: path.join(root, 'backend'), stdio: 'pipe', encoding: 'utf8' });
|
|
127
|
-
spinner.succeed(chalk.white('Backend build
|
|
128
|
-
} catch {
|
|
129
|
-
spinner.fail(chalk.red('Backend build
|
|
37
|
+
spinner.succeed(chalk.white('Backend build ✔'));
|
|
38
|
+
} catch (err) {
|
|
39
|
+
spinner.fail(chalk.red('Backend build falló: ' + (err.stderr || err.message || '').split('\n')[0]));
|
|
130
40
|
}
|
|
41
|
+
} else {
|
|
42
|
+
console.log(chalk.gray(' Backend: no tiene script build, omitiendo.'));
|
|
131
43
|
}
|
|
132
44
|
}
|
|
133
45
|
}
|
|
@@ -139,36 +51,145 @@ export async function runBuild(options) {
|
|
|
139
51
|
|
|
140
52
|
// ── novatec deploy ────────────────────────────────────────────────────────────
|
|
141
53
|
export async function runDeploy(options) {
|
|
54
|
+
const root = process.cwd();
|
|
55
|
+
const hasFe = await fse.pathExists(path.join(root, 'frontend'));
|
|
56
|
+
const hasBe = await fse.pathExists(path.join(root, 'backend'));
|
|
57
|
+
|
|
58
|
+
const deployFe = options.frontend || (!options.frontend && !options.backend);
|
|
59
|
+
const deployBe = options.backend || (!options.frontend && !options.backend);
|
|
60
|
+
|
|
61
|
+
// Verificar que estamos en un proyecto novatec
|
|
62
|
+
if (!hasFe && !hasBe) {
|
|
63
|
+
console.log(boxen(
|
|
64
|
+
chalk.yellow(' Ejecuta este comando dentro de un proyecto NovaTec\n\n') +
|
|
65
|
+
chalk.gray(' cd mi-proyecto\n') +
|
|
66
|
+
chalk.gray(' novatec deploy'),
|
|
67
|
+
{ padding: 1, margin: { left: 2 }, borderStyle: 'round', borderColor: 'yellow', dimBorder: true }
|
|
68
|
+
));
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
|
|
142
72
|
console.log();
|
|
143
73
|
console.log(boxen(
|
|
144
74
|
chalk.bold.white(' DEPLOY — NovaTec CLI\n\n') +
|
|
145
|
-
chalk.white(' Frontend
|
|
146
|
-
chalk.white(' Backend
|
|
147
|
-
chalk.gray(' Asegúrate de tener instalados:\n') +
|
|
148
|
-
chalk.gray(' npm install -g vercel\n') +
|
|
149
|
-
chalk.gray(' npm install -g @railway/cli'),
|
|
75
|
+
(deployFe && hasFe ? chalk.white(' ▲ Frontend → Vercel\n') : '') +
|
|
76
|
+
(deployBe && hasBe ? chalk.white(' ◆ Backend → Railway\n') : ''),
|
|
150
77
|
{ padding: 1, margin: { left: 2 }, borderStyle: 'round', borderColor: 'white', dimBorder: true }
|
|
151
78
|
));
|
|
152
79
|
|
|
153
|
-
|
|
80
|
+
// ── FRONTEND → VERCEL ───────────────────────────────────────────────────────
|
|
81
|
+
if (deployFe && hasFe) {
|
|
82
|
+
console.log(chalk.white('\n ▲ Desplegando frontend en Vercel...\n'));
|
|
154
83
|
|
|
155
|
-
|
|
84
|
+
// Instalar Vercel CLI si no está
|
|
156
85
|
if (!isInstalled('vercel')) {
|
|
157
|
-
console.log(chalk.
|
|
158
|
-
|
|
86
|
+
console.log(chalk.gray(' Instalando Vercel CLI...'));
|
|
87
|
+
try {
|
|
88
|
+
execSync('npm install -g vercel', { stdio: 'pipe' });
|
|
89
|
+
console.log(chalk.green(' ✔ Vercel CLI instalado'));
|
|
90
|
+
} catch (e) {
|
|
91
|
+
console.log(chalk.red(' ✖ Error instalando Vercel CLI: ' + e.message));
|
|
92
|
+
console.log(chalk.gray(' Instala manualmente: npm install -g vercel'));
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Verificar login
|
|
98
|
+
try {
|
|
99
|
+
execSync('vercel whoami', { stdio: 'pipe' });
|
|
100
|
+
} catch {
|
|
101
|
+
console.log(chalk.yellow(' No estás logueado en Vercel. Iniciando login...\n'));
|
|
102
|
+
spawnSync('vercel', ['login'], { cwd: path.join(root, 'frontend'), stdio: 'inherit' });
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Deploy
|
|
106
|
+
console.log(chalk.gray(' Ejecutando: vercel --prod\n'));
|
|
107
|
+
const r = spawnSync('vercel', ['--prod'], {
|
|
108
|
+
cwd: path.join(root, 'frontend'),
|
|
109
|
+
stdio: 'inherit',
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
if (r.status === 0) {
|
|
113
|
+
console.log(chalk.green('\n ✔ Frontend desplegado en Vercel'));
|
|
114
|
+
} else {
|
|
115
|
+
console.log(chalk.red('\n ✖ Error desplegando frontend'));
|
|
159
116
|
}
|
|
160
|
-
console.log(chalk.white('\n Desplegando frontend en Vercel...'));
|
|
161
|
-
execSync('vercel --prod', { cwd: path.join(root, 'frontend'), stdio: 'inherit' });
|
|
162
117
|
}
|
|
163
118
|
|
|
164
|
-
|
|
119
|
+
// ── BACKEND → RAILWAY ───────────────────────────────────────────────────────
|
|
120
|
+
if (deployBe && hasBe) {
|
|
121
|
+
console.log(chalk.white('\n ◆ Desplegando backend en Railway...\n'));
|
|
122
|
+
|
|
123
|
+
// Instalar Railway CLI si no está
|
|
165
124
|
if (!isInstalled('railway')) {
|
|
166
|
-
console.log(chalk.
|
|
167
|
-
|
|
125
|
+
console.log(chalk.gray(' Instalando Railway CLI...'));
|
|
126
|
+
try {
|
|
127
|
+
execSync('npm install -g @railway/cli', { stdio: 'pipe' });
|
|
128
|
+
console.log(chalk.green(' ✔ Railway CLI instalado'));
|
|
129
|
+
} catch (e) {
|
|
130
|
+
console.log(chalk.red(' ✖ Error instalando Railway CLI: ' + e.message));
|
|
131
|
+
console.log(chalk.gray(' Instala manualmente: npm install -g @railway/cli'));
|
|
132
|
+
process.exit(1);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Verificar login
|
|
137
|
+
try {
|
|
138
|
+
execSync('railway whoami', { stdio: 'pipe' });
|
|
139
|
+
} catch {
|
|
140
|
+
console.log(chalk.yellow(' No estás logueado en Railway. Iniciando login...\n'));
|
|
141
|
+
spawnSync('railway', ['login'], { stdio: 'inherit' });
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Verificar si hay proyecto Railway vinculado
|
|
145
|
+
const railwayJson = path.join(root, 'backend', '.railway');
|
|
146
|
+
const hasRailwayProject = await fse.pathExists(railwayJson);
|
|
147
|
+
|
|
148
|
+
if (!hasRailwayProject) {
|
|
149
|
+
console.log(chalk.yellow(' No hay proyecto Railway vinculado.'));
|
|
150
|
+
console.log(chalk.gray(' Vinculando proyecto (se abrirá selector)...\n'));
|
|
151
|
+
const link = spawnSync('railway', ['link'], {
|
|
152
|
+
cwd: path.join(root, 'backend'),
|
|
153
|
+
stdio: 'inherit',
|
|
154
|
+
});
|
|
155
|
+
if (link.status !== 0) {
|
|
156
|
+
console.log(chalk.gray('\n O crea uno nuevo con: railway init'));
|
|
157
|
+
console.log(chalk.gray(' Luego ejecuta: novatec deploy --backend'));
|
|
158
|
+
process.exit(1);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Deploy
|
|
163
|
+
console.log(chalk.gray(' Ejecutando: railway up\n'));
|
|
164
|
+
const r = spawnSync('railway', ['up', '--detach'], {
|
|
165
|
+
cwd: path.join(root, 'backend'),
|
|
166
|
+
stdio: 'inherit',
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
if (r.status === 0) {
|
|
170
|
+
console.log(chalk.green('\n ✔ Backend desplegado en Railway'));
|
|
171
|
+
console.log(chalk.gray(' Ver logs: railway logs'));
|
|
172
|
+
console.log(chalk.gray(' Abrir: railway open'));
|
|
173
|
+
} else {
|
|
174
|
+
console.log(chalk.red('\n ✖ Error desplegando backend'));
|
|
175
|
+
console.log(chalk.gray(' Ver logs: railway logs'));
|
|
168
176
|
}
|
|
169
|
-
console.log(chalk.white('\n Desplegando backend en Railway...'));
|
|
170
|
-
execSync('railway up', { cwd: path.join(root, 'backend'), stdio: 'inherit' });
|
|
171
177
|
}
|
|
172
178
|
|
|
179
|
+
// ── RESUMEN ─────────────────────────────────────────────────────────────────
|
|
180
|
+
console.log();
|
|
181
|
+
console.log(boxen(
|
|
182
|
+
chalk.bold.white(' GUÍA RÁPIDA\n\n') +
|
|
183
|
+
chalk.white(' Vercel (Frontend)\n') +
|
|
184
|
+
chalk.gray(' 1. vercel login\n') +
|
|
185
|
+
chalk.gray(' 2. cd frontend && vercel --prod\n') +
|
|
186
|
+
chalk.gray(' 3. Variables de entorno → vercel.com/dashboard\n\n') +
|
|
187
|
+
chalk.white(' Railway (Backend)\n') +
|
|
188
|
+
chalk.gray(' 1. railway login\n') +
|
|
189
|
+
chalk.gray(' 2. cd backend && railway init\n') +
|
|
190
|
+
chalk.gray(' 3. railway up\n') +
|
|
191
|
+
chalk.gray(' 4. Variables de entorno → railway.app/dashboard'),
|
|
192
|
+
{ padding: 1, margin: { left: 2 }, borderStyle: 'single', borderColor: 'gray', dimBorder: true }
|
|
193
|
+
));
|
|
173
194
|
console.log();
|
|
174
195
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
2
|
import chalk from 'chalk';
|
|
3
3
|
import fse from 'fs-extra';
|
|
4
|
+
import gradient from 'gradient-string';
|
|
4
5
|
import { execSync } from 'child_process';
|
|
5
6
|
|
|
6
|
-
// Comandos de scaffolding por framework
|
|
7
7
|
const SCAFFOLD = {
|
|
8
8
|
react: 'npm create vite@latest frontend -- --template react',
|
|
9
9
|
vue: 'npm create vite@latest frontend -- --template vue',
|
|
@@ -17,11 +17,26 @@ const SCAFFOLD = {
|
|
|
17
17
|
qwik: 'npm create qwik@latest frontend -- --no-install',
|
|
18
18
|
};
|
|
19
19
|
|
|
20
|
+
const COLORS = {
|
|
21
|
+
react: ['#61DAFB', '#21D4FD'],
|
|
22
|
+
next: ['#ffffff', '#888888'],
|
|
23
|
+
vue: ['#42b883', '#35495e'],
|
|
24
|
+
nuxt: ['#00DC82', '#003543'],
|
|
25
|
+
astro: ['#FF5D01', '#BC52EE'],
|
|
26
|
+
svelte: ['#FF3E00', '#FF8C00'],
|
|
27
|
+
solid: ['#2C4F7C', '#446B9E'],
|
|
28
|
+
angular: ['#DD0031', '#C3002F'],
|
|
29
|
+
remix: ['#818CF8', '#6366F1'],
|
|
30
|
+
qwik: ['#18B6F6', '#AC7EF4'],
|
|
31
|
+
};
|
|
32
|
+
|
|
20
33
|
export async function generateFrontend(config, projectPath) {
|
|
21
34
|
const cmd = SCAFFOLD[config.frontend];
|
|
22
35
|
if (!cmd) throw new Error(`Frontend no soportado: ${config.frontend}`);
|
|
23
36
|
|
|
24
|
-
|
|
37
|
+
const [c1, c2] = COLORS[config.frontend] || ['#ffffff', '#aaaaaa'];
|
|
38
|
+
const label = gradient(c1, c2)(` ◆ ${config.frontend.toUpperCase()} `);
|
|
39
|
+
console.log(chalk.blue(`\n ${label} ${chalk.gray('Generando frontend...')}`));
|
|
25
40
|
|
|
26
41
|
try {
|
|
27
42
|
execSync(cmd, { cwd: projectPath, stdio: 'pipe' });
|
package/lib/prompts.js
CHANGED
|
@@ -1,54 +1,88 @@
|
|
|
1
1
|
import inquirer from 'inquirer';
|
|
2
2
|
import chalk from 'chalk';
|
|
3
|
+
import gradient from 'gradient-string';
|
|
3
4
|
import fse from 'fs-extra';
|
|
4
5
|
import path from 'path';
|
|
5
6
|
import boxen from 'boxen';
|
|
6
7
|
|
|
8
|
+
// Colores reales de cada framework/DB
|
|
9
|
+
const g = {
|
|
10
|
+
react: gradient('#61DAFB', '#21D4FD'),
|
|
11
|
+
next: gradient('#ffffff', '#888888'),
|
|
12
|
+
vue: gradient('#42b883', '#35495e'),
|
|
13
|
+
nuxt: gradient('#00DC82', '#003543'),
|
|
14
|
+
astro: gradient('#FF5D01', '#BC52EE'),
|
|
15
|
+
svelte: gradient('#FF3E00', '#FF8C00'),
|
|
16
|
+
solid: gradient('#2C4F7C', '#446B9E'),
|
|
17
|
+
angular: gradient('#DD0031', '#C3002F'),
|
|
18
|
+
remix: gradient('#121212', '#818CF8'),
|
|
19
|
+
qwik: gradient('#18B6F6', '#AC7EF4'),
|
|
20
|
+
express: gradient('#ffffff', '#aaaaaa'),
|
|
21
|
+
nestjs: gradient('#E0234E', '#FF6B6B'),
|
|
22
|
+
fastify: gradient('#000000', '#888888'),
|
|
23
|
+
hono: gradient('#FF5733', '#FF8C00'),
|
|
24
|
+
fastapi: gradient('#009688', '#26A69A'),
|
|
25
|
+
django: gradient('#092E20', '#44B78B'),
|
|
26
|
+
flask: gradient('#ffffff', '#aaaaaa'),
|
|
27
|
+
spring: gradient('#6DB33F', '#77BC1F'),
|
|
28
|
+
deno: gradient('#ffffff', '#aaaaaa'),
|
|
29
|
+
gin: gradient('#00ACD7', '#5DC9E2'),
|
|
30
|
+
postgres: gradient('#336791', '#4A90D9'),
|
|
31
|
+
mysql: gradient('#4479A1', '#00758F'),
|
|
32
|
+
mongo: gradient('#47A248', '#00ED64'),
|
|
33
|
+
sqlite: gradient('#003B57', '#0F80CC'),
|
|
34
|
+
supabase: gradient('#3ECF8E', '#1C1C1C'),
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
function logo(key, text) {
|
|
38
|
+
return g[key] ? g[key](text) : chalk.white(text);
|
|
39
|
+
}
|
|
40
|
+
|
|
7
41
|
export const FRONTEND_CHOICES = [
|
|
8
|
-
{ name: 'React
|
|
9
|
-
{ name: 'Next.js
|
|
10
|
-
{ name: 'Vue 3
|
|
11
|
-
{ name: 'Nuxt 3
|
|
12
|
-
{ name: 'Astro
|
|
13
|
-
{ name: 'SvelteKit
|
|
14
|
-
{ name: 'SolidJS
|
|
15
|
-
{ name: 'Angular
|
|
16
|
-
{ name: 'Remix
|
|
17
|
-
{ name: 'Qwik
|
|
42
|
+
{ name: logo('react', ' ◆ React ') + chalk.gray(' — Vite + React 18'), value: 'react' },
|
|
43
|
+
{ name: logo('next', ' ▲ Next.js ') + chalk.gray(' — SSR/SSG App Router'), value: 'next' },
|
|
44
|
+
{ name: logo('vue', ' ◈ Vue 3 ') + chalk.gray(' — Vite + Composition API'), value: 'vue' },
|
|
45
|
+
{ name: logo('nuxt', ' ◈ Nuxt 3 ') + chalk.gray(' — Vue SSR/SSG'), value: 'nuxt' },
|
|
46
|
+
{ name: logo('astro', ' ✦ Astro ') + chalk.gray(' — MPA ultra-rápido'), value: 'astro' },
|
|
47
|
+
{ name: logo('svelte', ' ◆ SvelteKit ') + chalk.gray(' — Svelte SSR/SSG'), value: 'svelte' },
|
|
48
|
+
{ name: logo('solid', ' ◆ SolidJS ') + chalk.gray(' — Sin Virtual DOM'), value: 'solid' },
|
|
49
|
+
{ name: logo('angular', ' ◆ Angular ') + chalk.gray(' — Framework empresarial'), value: 'angular' },
|
|
50
|
+
{ name: logo('remix', ' ◆ Remix ') + chalk.gray(' — Full-stack React'), value: 'remix' },
|
|
51
|
+
{ name: logo('qwik', ' ⚡ Qwik ') + chalk.gray(' — Resumability'), value: 'qwik' },
|
|
18
52
|
];
|
|
19
53
|
|
|
20
54
|
export const BACKEND_CHOICES = [
|
|
21
|
-
{ name: 'Express
|
|
22
|
-
{ name: 'NestJS
|
|
23
|
-
{ name: 'Fastify
|
|
24
|
-
{ name: 'Hono
|
|
25
|
-
{ name: 'FastAPI
|
|
26
|
-
{ name: 'Django
|
|
27
|
-
{ name: 'Flask
|
|
28
|
-
{ name: 'Spring Boot — Empresarial Java', value: 'spring' },
|
|
29
|
-
{ name: 'Deno/Oak
|
|
30
|
-
{ name: 'Go/Gin
|
|
55
|
+
{ name: logo('express', ' ◆ Express ') + chalk.gray(' — Minimalista Node.js'), value: 'express' },
|
|
56
|
+
{ name: logo('nestjs', ' ◆ NestJS ') + chalk.gray(' — Modular TypeScript'), value: 'nestjs' },
|
|
57
|
+
{ name: logo('fastify', ' ⚡ Fastify ') + chalk.gray(' — Ultra-rápido Node'), value: 'fastify' },
|
|
58
|
+
{ name: logo('hono', ' ◆ Hono ') + chalk.gray(' — Edge-ready ligero'), value: 'hono' },
|
|
59
|
+
{ name: logo('fastapi', ' ◆ FastAPI ') + chalk.gray(' — Async Python'), value: 'fastapi' },
|
|
60
|
+
{ name: logo('django', ' ◆ Django ') + chalk.gray(' — Baterías incluidas'), value: 'django' },
|
|
61
|
+
{ name: logo('flask', ' ◆ Flask ') + chalk.gray(' — Micro-framework Python'), value: 'flask' },
|
|
62
|
+
{ name: logo('spring', ' ◆ Spring Boot') + chalk.gray(' — Empresarial Java'), value: 'spring' },
|
|
63
|
+
{ name: logo('deno', ' ◆ Deno/Oak ') + chalk.gray(' — TypeScript seguro'), value: 'deno' },
|
|
64
|
+
{ name: logo('gin', ' ◆ Go/Gin ') + chalk.gray(' — Rendimiento extremo'), value: 'gin' },
|
|
31
65
|
];
|
|
32
66
|
|
|
33
67
|
export const DB_CHOICES = [
|
|
34
|
-
{ name: 'PostgreSQL
|
|
35
|
-
{ name: 'MySQL
|
|
36
|
-
{ name: 'MongoDB
|
|
37
|
-
{ name: 'SQLite
|
|
38
|
-
{ name: 'Supabase
|
|
39
|
-
{ name: 'Ninguna',
|
|
68
|
+
{ name: logo('postgres', ' ◆ PostgreSQL ') + chalk.gray(' — Relacional robusto'), value: 'postgres' },
|
|
69
|
+
{ name: logo('mysql', ' ◆ MySQL ') + chalk.gray(' — Relacional popular'), value: 'mysql' },
|
|
70
|
+
{ name: logo('mongo', ' ◆ MongoDB ') + chalk.gray(' — NoSQL documentos'), value: 'mongo' },
|
|
71
|
+
{ name: logo('sqlite', ' ◆ SQLite ') + chalk.gray(' — Embebido ligero'), value: 'sqlite' },
|
|
72
|
+
{ name: logo('supabase', ' ◆ Supabase ') + chalk.gray(' — BaaS con Auth incluida'), value: 'supabase' },
|
|
73
|
+
{ name: chalk.gray(' ○ Ninguna'), value: 'none' },
|
|
40
74
|
];
|
|
41
75
|
|
|
42
76
|
export const ARCH_CHOICES = [
|
|
43
|
-
{ name: 'MVC
|
|
44
|
-
{ name: 'Hexagonal
|
|
45
|
-
{ name: 'Clean
|
|
77
|
+
{ name: chalk.white(' ◆ MVC ') + chalk.gray(' — Model-View-Controller (clásico)'), value: 'mvc' },
|
|
78
|
+
{ name: chalk.white(' ◆ Hexagonal ') + chalk.gray(' — Ports & Adapters'), value: 'hexagonal' },
|
|
79
|
+
{ name: chalk.white(' ◆ Clean ') + chalk.gray(' — Clean Architecture (capas)'), value: 'clean' },
|
|
46
80
|
];
|
|
47
81
|
|
|
48
82
|
export const PM_CHOICES = [
|
|
49
|
-
{ name: 'npm', value: 'npm' },
|
|
50
|
-
{ name: 'pnpm', value: 'pnpm' },
|
|
51
|
-
{ name: 'yarn', value: 'yarn' },
|
|
83
|
+
{ name: chalk.white(' ◆ npm'), value: 'npm' },
|
|
84
|
+
{ name: chalk.white(' ◆ pnpm'), value: 'pnpm' },
|
|
85
|
+
{ name: chalk.white(' ◆ yarn'), value: 'yarn' },
|
|
52
86
|
];
|
|
53
87
|
|
|
54
88
|
export async function askProjectOptions(nameArg, options) {
|
package/package.json
CHANGED