novatec-cli 3.0.7 → 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/lib/generators/deploy.js +137 -116
- package/package.json +1 -1
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
|
}
|
package/package.json
CHANGED