@openfactu/cli 0.0.2 → 0.0.4
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/README.md +121 -0
- package/dist/bin/openfactu.js +0 -0
- package/dist/src/commands/deploy.d.ts +2 -0
- package/dist/src/commands/deploy.js +370 -0
- package/dist/src/commands/install.d.ts +2 -0
- package/dist/src/commands/install.js +325 -0
- package/dist/src/commands/migrate.js +5 -4
- package/dist/src/commands/plugin.js +4 -5
- package/dist/src/commands/setup.js +8 -8
- package/dist/src/commands/tenant.js +3 -3
- package/dist/src/commands/update.js +20 -20
- package/dist/src/index.js +5 -1
- package/dist/src/utils/db.d.ts +3 -2
- package/dist/src/utils/db.js +20 -10
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# @openfactu/cli
|
|
2
|
+
|
|
3
|
+
CLI oficial para instalar, gestionar y desplegar [OpenFactu](https://github.com/AngelAcedo12/OpenFactu) -- ERP de facturación open source.
|
|
4
|
+
|
|
5
|
+
## Instalacion
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm i -g @openfactu/cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Inicio rapido
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# Descargar e instalar OpenFactu (te deja elegir version)
|
|
15
|
+
openfactu install
|
|
16
|
+
|
|
17
|
+
# Configurar base de datos y usuario admin
|
|
18
|
+
openfactu setup
|
|
19
|
+
|
|
20
|
+
# Aplicar migraciones
|
|
21
|
+
openfactu migrate
|
|
22
|
+
|
|
23
|
+
# Desplegar en red local o internet
|
|
24
|
+
openfactu deploy
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Comandos
|
|
28
|
+
|
|
29
|
+
### Instalacion y actualizacion
|
|
30
|
+
|
|
31
|
+
| Comando | Descripcion |
|
|
32
|
+
|---------|-------------|
|
|
33
|
+
| `openfactu install [dir]` | Descarga e instala OpenFactu. Muestra las releases de GitHub para elegir version. Soporta Docker en Windows/Mac/Linux. |
|
|
34
|
+
| `openfactu update` | Actualiza a la ultima version desde GitHub sin perder datos (plugins, storage, .env se preservan). |
|
|
35
|
+
| `openfactu update:check` | Comprueba si hay versiones nuevas disponibles. |
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
# Instalar una release especifica
|
|
39
|
+
openfactu install ./mi-erp --tag v1.2.0
|
|
40
|
+
|
|
41
|
+
# Instalar desde una branch
|
|
42
|
+
openfactu install ./mi-erp --branch develop
|
|
43
|
+
|
|
44
|
+
# Actualizar la instalacion actual
|
|
45
|
+
openfactu update
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Despliegue
|
|
49
|
+
|
|
50
|
+
| Comando | Descripcion |
|
|
51
|
+
|---------|-------------|
|
|
52
|
+
| `openfactu deploy` | Wizard para configurar acceso externo: red local, dominio publico o localhost. Genera `docker-compose.prod.yml`. |
|
|
53
|
+
| `openfactu deploy:status` | Muestra el estado de los contenedores Docker y las URLs de acceso. |
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
# Configurar para que sea accesible en la red
|
|
57
|
+
openfactu deploy
|
|
58
|
+
|
|
59
|
+
# Ver estado de los servicios
|
|
60
|
+
openfactu deploy:status
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Base de datos
|
|
64
|
+
|
|
65
|
+
| Comando | Descripcion |
|
|
66
|
+
|---------|-------------|
|
|
67
|
+
| `openfactu setup` | Configuracion inicial: verifica BD, crea admin, primer tenant. |
|
|
68
|
+
| `openfactu migrate` | Ejecuta migraciones pendientes en todos los tenants. |
|
|
69
|
+
| `openfactu migrate:status` | Muestra tabla con estado de migraciones por tenant. |
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
# Migrar solo un tenant especifico
|
|
73
|
+
openfactu migrate --tenant "Mi Empresa"
|
|
74
|
+
|
|
75
|
+
# Ver que migraciones faltan
|
|
76
|
+
openfactu migrate:status
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Tenants (empresas)
|
|
80
|
+
|
|
81
|
+
| Comando | Descripcion |
|
|
82
|
+
|---------|-------------|
|
|
83
|
+
| `openfactu tenant list` | Lista todas las empresas. |
|
|
84
|
+
| `openfactu tenant create [nombre]` | Crea una empresa nueva con schema y migraciones. |
|
|
85
|
+
| `openfactu tenant sync [nombre]` | Sincroniza migraciones de un tenant o todos. |
|
|
86
|
+
|
|
87
|
+
### Plugins
|
|
88
|
+
|
|
89
|
+
| Comando | Descripcion |
|
|
90
|
+
|---------|-------------|
|
|
91
|
+
| `openfactu plugin list` | Lista plugins instalados con su estado por tenant. |
|
|
92
|
+
|
|
93
|
+
### Otros
|
|
94
|
+
|
|
95
|
+
| Comando | Descripcion |
|
|
96
|
+
|---------|-------------|
|
|
97
|
+
| `openfactu version` | Muestra versiones del CLI, server, web y Node. |
|
|
98
|
+
|
|
99
|
+
## Uso desde cualquier directorio
|
|
100
|
+
|
|
101
|
+
El CLI detecta automaticamente la instalacion de OpenFactu. Si no estas dentro del proyecto:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
# Opcion 1: flag --path
|
|
105
|
+
openfactu --path /ruta/a/openfactu migrate
|
|
106
|
+
|
|
107
|
+
# Opcion 2: variable de entorno
|
|
108
|
+
export OPENFACTU_HOME=/ruta/a/openfactu
|
|
109
|
+
openfactu migrate
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Requisitos
|
|
113
|
+
|
|
114
|
+
- Node.js >= 18
|
|
115
|
+
- Docker Desktop (para instalar y desplegar)
|
|
116
|
+
- Git (para descargar releases)
|
|
117
|
+
|
|
118
|
+
## Links
|
|
119
|
+
|
|
120
|
+
- [GitHub](https://github.com/AngelAcedo12/OpenFactu)
|
|
121
|
+
- [Reportar un problema](https://github.com/AngelAcedo12/OpenFactu/issues)
|
package/dist/bin/openfactu.js
CHANGED
|
File without changes
|
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.registerDeployCommand = registerDeployCommand;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const ora_1 = __importDefault(require("ora"));
|
|
9
|
+
const inquirer_1 = __importDefault(require("inquirer"));
|
|
10
|
+
const child_process_1 = require("child_process");
|
|
11
|
+
const fs_1 = __importDefault(require("fs"));
|
|
12
|
+
const path_1 = __importDefault(require("path"));
|
|
13
|
+
const os_1 = __importDefault(require("os"));
|
|
14
|
+
const logger_1 = require("../utils/logger");
|
|
15
|
+
const paths_1 = require("../utils/paths");
|
|
16
|
+
function getLocalIPs() {
|
|
17
|
+
const interfaces = os_1.default.networkInterfaces();
|
|
18
|
+
const ips = [];
|
|
19
|
+
for (const iface of Object.values(interfaces)) {
|
|
20
|
+
if (!iface)
|
|
21
|
+
continue;
|
|
22
|
+
for (const info of iface) {
|
|
23
|
+
if (info.family === 'IPv4' && !info.internal) {
|
|
24
|
+
ips.push(info.address);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return ips;
|
|
29
|
+
}
|
|
30
|
+
function readEnv(envPath) {
|
|
31
|
+
const env = {};
|
|
32
|
+
if (!fs_1.default.existsSync(envPath))
|
|
33
|
+
return env;
|
|
34
|
+
const lines = fs_1.default.readFileSync(envPath, 'utf-8').split('\n');
|
|
35
|
+
for (const line of lines) {
|
|
36
|
+
const trimmed = line.trim();
|
|
37
|
+
if (!trimmed || trimmed.startsWith('#'))
|
|
38
|
+
continue;
|
|
39
|
+
const eqIdx = trimmed.indexOf('=');
|
|
40
|
+
if (eqIdx > 0) {
|
|
41
|
+
env[trimmed.substring(0, eqIdx).trim()] = trimmed.substring(eqIdx + 1).trim();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return env;
|
|
45
|
+
}
|
|
46
|
+
function writeEnv(envPath, env) {
|
|
47
|
+
const lines = [];
|
|
48
|
+
for (const [key, value] of Object.entries(env)) {
|
|
49
|
+
lines.push(`${key}=${value}`);
|
|
50
|
+
}
|
|
51
|
+
fs_1.default.writeFileSync(envPath, lines.join('\n') + '\n');
|
|
52
|
+
}
|
|
53
|
+
function registerDeployCommand(program) {
|
|
54
|
+
program
|
|
55
|
+
.command('deploy')
|
|
56
|
+
.description('Configura OpenFactu para producción (acceso externo)')
|
|
57
|
+
.action(async () => {
|
|
58
|
+
console.log();
|
|
59
|
+
console.log(chalk_1.default.bold.white(' OpenFactu — Configurar Despliegue'));
|
|
60
|
+
console.log(chalk_1.default.dim(' ────────────────────────────────────'));
|
|
61
|
+
console.log();
|
|
62
|
+
try {
|
|
63
|
+
const root = (0, paths_1.getProjectRoot)();
|
|
64
|
+
const envPath = path_1.default.join(root, '.env');
|
|
65
|
+
const composePath = path_1.default.join(root, 'docker-compose.yml');
|
|
66
|
+
// 1. Detectar IPs locales
|
|
67
|
+
const localIPs = getLocalIPs();
|
|
68
|
+
logger_1.log.info(`IPs detectadas: ${localIPs.join(', ') || 'ninguna'}`);
|
|
69
|
+
// 2. Preguntar configuración
|
|
70
|
+
const { mode } = await inquirer_1.default.prompt([
|
|
71
|
+
{
|
|
72
|
+
type: 'list',
|
|
73
|
+
name: 'mode',
|
|
74
|
+
message: 'Tipo de despliegue:',
|
|
75
|
+
choices: [
|
|
76
|
+
{ name: `${chalk_1.default.green('Red local')} ${chalk_1.default.dim('— accesible desde otros equipos en tu red')}`, value: 'lan' },
|
|
77
|
+
{ name: `${chalk_1.default.cyan('Dominio/IP pública')} ${chalk_1.default.dim('— accesible desde internet')}`, value: 'public' },
|
|
78
|
+
{ name: `${chalk_1.default.dim('Solo localhost')} ${chalk_1.default.dim('— solo este equipo')}`, value: 'localhost' },
|
|
79
|
+
],
|
|
80
|
+
},
|
|
81
|
+
]);
|
|
82
|
+
let host = 'localhost';
|
|
83
|
+
let serverPort = '3000';
|
|
84
|
+
let webPort = '8080';
|
|
85
|
+
let useSSL = false;
|
|
86
|
+
if (mode === 'lan') {
|
|
87
|
+
const ipChoices = localIPs.map((ip) => ({ name: ip, value: ip }));
|
|
88
|
+
ipChoices.push({ name: 'Otra (escribir manualmente)', value: '__custom__' });
|
|
89
|
+
const { selectedIP } = await inquirer_1.default.prompt([
|
|
90
|
+
{
|
|
91
|
+
type: 'list',
|
|
92
|
+
name: 'selectedIP',
|
|
93
|
+
message: 'IP de la máquina en la red:',
|
|
94
|
+
choices: ipChoices,
|
|
95
|
+
},
|
|
96
|
+
]);
|
|
97
|
+
if (selectedIP === '__custom__') {
|
|
98
|
+
const { customIP } = await inquirer_1.default.prompt([
|
|
99
|
+
{ type: 'input', name: 'customIP', message: 'IP:' },
|
|
100
|
+
]);
|
|
101
|
+
host = customIP;
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
host = selectedIP;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
else if (mode === 'public') {
|
|
108
|
+
const { domain } = await inquirer_1.default.prompt([
|
|
109
|
+
{
|
|
110
|
+
type: 'input',
|
|
111
|
+
name: 'domain',
|
|
112
|
+
message: 'Dominio o IP pública (ej: erp.miempresa.com):',
|
|
113
|
+
},
|
|
114
|
+
]);
|
|
115
|
+
host = domain;
|
|
116
|
+
const { ssl } = await inquirer_1.default.prompt([
|
|
117
|
+
{
|
|
118
|
+
type: 'confirm',
|
|
119
|
+
name: 'ssl',
|
|
120
|
+
message: '¿Usar HTTPS (SSL)?',
|
|
121
|
+
default: true,
|
|
122
|
+
},
|
|
123
|
+
]);
|
|
124
|
+
useSSL = ssl;
|
|
125
|
+
}
|
|
126
|
+
// Puertos
|
|
127
|
+
const { ports } = await inquirer_1.default.prompt([
|
|
128
|
+
{
|
|
129
|
+
type: 'confirm',
|
|
130
|
+
name: 'ports',
|
|
131
|
+
message: `¿Usar puertos por defecto? (web: 8080, api: 3000)`,
|
|
132
|
+
default: true,
|
|
133
|
+
},
|
|
134
|
+
]);
|
|
135
|
+
if (!ports) {
|
|
136
|
+
const answers = await inquirer_1.default.prompt([
|
|
137
|
+
{ type: 'input', name: 'webPort', message: 'Puerto web:', default: '8080' },
|
|
138
|
+
{ type: 'input', name: 'serverPort', message: 'Puerto API:', default: '3000' },
|
|
139
|
+
]);
|
|
140
|
+
webPort = answers.webPort;
|
|
141
|
+
serverPort = answers.serverPort;
|
|
142
|
+
}
|
|
143
|
+
// Password de BD
|
|
144
|
+
const { dbPassword } = await inquirer_1.default.prompt([
|
|
145
|
+
{
|
|
146
|
+
type: 'input',
|
|
147
|
+
name: 'dbPassword',
|
|
148
|
+
message: 'Password de PostgreSQL:',
|
|
149
|
+
default: 'openfactu_pass',
|
|
150
|
+
},
|
|
151
|
+
]);
|
|
152
|
+
// 3. Construir configuración
|
|
153
|
+
const protocol = useSSL ? 'https' : 'http';
|
|
154
|
+
const webUrl = webPort === '80' || webPort === '443'
|
|
155
|
+
? `${protocol}://${host}`
|
|
156
|
+
: `${protocol}://${host}:${webPort}`;
|
|
157
|
+
const apiUrl = serverPort === '80' || serverPort === '443'
|
|
158
|
+
? `${protocol}://${host}`
|
|
159
|
+
: `${protocol}://${host}:${serverPort}`;
|
|
160
|
+
logger_1.log.blank();
|
|
161
|
+
logger_1.log.title(' Resumen de configuración');
|
|
162
|
+
logger_1.log.info(`Web: ${chalk_1.default.cyan(webUrl)}`);
|
|
163
|
+
logger_1.log.info(`API: ${chalk_1.default.cyan(apiUrl)}`);
|
|
164
|
+
logger_1.log.info(`BD Password: ${chalk_1.default.dim(dbPassword === 'openfactu_pass' ? '(default)' : '****')}`);
|
|
165
|
+
logger_1.log.info(`SSL: ${useSSL ? chalk_1.default.green('Si') : chalk_1.default.dim('No')}`);
|
|
166
|
+
logger_1.log.blank();
|
|
167
|
+
const { confirm } = await inquirer_1.default.prompt([
|
|
168
|
+
{ type: 'confirm', name: 'confirm', message: 'Aplicar configuración?', default: true },
|
|
169
|
+
]);
|
|
170
|
+
if (!confirm) {
|
|
171
|
+
logger_1.log.info('Cancelado');
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
// 4. Escribir .env
|
|
175
|
+
const envSpinner = (0, ora_1.default)('Configurando .env...').start();
|
|
176
|
+
const env = readEnv(envPath);
|
|
177
|
+
env.SERVER_PORT = serverPort;
|
|
178
|
+
env.WEB_PORT = webPort;
|
|
179
|
+
env.DB_PORT = env.DB_PORT || '5432';
|
|
180
|
+
env.POSTGRES_USER = env.POSTGRES_USER || 'openfactu';
|
|
181
|
+
env.POSTGRES_PASSWORD = dbPassword;
|
|
182
|
+
env.POSTGRES_DB = env.POSTGRES_DB || 'openfactudb';
|
|
183
|
+
env.DATABASE_URL = `postgresql://${env.POSTGRES_USER}:${dbPassword}@db:5432/${env.POSTGRES_DB}`;
|
|
184
|
+
env.VITE_API_URL = apiUrl;
|
|
185
|
+
env.HOST = host;
|
|
186
|
+
env.CORS_ORIGIN = webUrl;
|
|
187
|
+
writeEnv(envPath, env);
|
|
188
|
+
envSpinner.succeed('.env configurado');
|
|
189
|
+
// 5. Generar docker-compose.prod.yml con bind a 0.0.0.0
|
|
190
|
+
const prodComposePath = path_1.default.join(root, 'docker-compose.prod.yml');
|
|
191
|
+
const prodSpinner = (0, ora_1.default)('Generando docker-compose.prod.yml...').start();
|
|
192
|
+
let composeContent = `version: '3.8'
|
|
193
|
+
|
|
194
|
+
services:
|
|
195
|
+
web:
|
|
196
|
+
build:
|
|
197
|
+
context: .
|
|
198
|
+
dockerfile: apps/web/Dockerfile
|
|
199
|
+
args:
|
|
200
|
+
VITE_API_URL: "${apiUrl}"
|
|
201
|
+
ports:
|
|
202
|
+
- "0.0.0.0:${webPort}:80"
|
|
203
|
+
environment:
|
|
204
|
+
VITE_API_URL: "${apiUrl}"
|
|
205
|
+
depends_on:
|
|
206
|
+
- server
|
|
207
|
+
restart: unless-stopped
|
|
208
|
+
networks:
|
|
209
|
+
- openfactu_net
|
|
210
|
+
|
|
211
|
+
server:
|
|
212
|
+
build:
|
|
213
|
+
context: .
|
|
214
|
+
dockerfile: apps/server/Dockerfile
|
|
215
|
+
ports:
|
|
216
|
+
- "0.0.0.0:${serverPort}:3000"
|
|
217
|
+
env_file:
|
|
218
|
+
- .env
|
|
219
|
+
volumes:
|
|
220
|
+
- ./plugins:/app/plugins
|
|
221
|
+
- ./storage:/app/storage
|
|
222
|
+
depends_on:
|
|
223
|
+
- db
|
|
224
|
+
environment:
|
|
225
|
+
- DATABASE_URL=postgresql://\${POSTGRES_USER:-openfactu}:\${POSTGRES_PASSWORD:-openfactu_pass}@db:5432/\${POSTGRES_DB:-openfactudb}
|
|
226
|
+
- CORS_ORIGIN=${webUrl}
|
|
227
|
+
- NODE_ENV=production
|
|
228
|
+
restart: unless-stopped
|
|
229
|
+
networks:
|
|
230
|
+
- openfactu_net
|
|
231
|
+
|
|
232
|
+
db:
|
|
233
|
+
image: postgres:15-alpine
|
|
234
|
+
environment:
|
|
235
|
+
POSTGRES_USER: \${POSTGRES_USER:-openfactu}
|
|
236
|
+
POSTGRES_PASSWORD: \${POSTGRES_PASSWORD:-openfactu_pass}
|
|
237
|
+
POSTGRES_DB: \${POSTGRES_DB:-openfactudb}
|
|
238
|
+
ports:
|
|
239
|
+
- "127.0.0.1:\${DB_PORT:-5432}:5432"
|
|
240
|
+
volumes:
|
|
241
|
+
- ./storage/db_data:/var/lib/postgresql/data
|
|
242
|
+
restart: unless-stopped
|
|
243
|
+
networks:
|
|
244
|
+
- openfactu_net
|
|
245
|
+
|
|
246
|
+
networks:
|
|
247
|
+
openfactu_net:
|
|
248
|
+
driver: bridge
|
|
249
|
+
`;
|
|
250
|
+
// Si SSL, añadir nginx reverse proxy
|
|
251
|
+
if (useSSL) {
|
|
252
|
+
composeContent += `
|
|
253
|
+
# Para SSL, configura un reverse proxy (nginx, traefik, caddy) delante.
|
|
254
|
+
# Ejemplo con Caddy (descomentar):
|
|
255
|
+
#
|
|
256
|
+
# caddy:
|
|
257
|
+
# image: caddy:2-alpine
|
|
258
|
+
# ports:
|
|
259
|
+
# - "0.0.0.0:80:80"
|
|
260
|
+
# - "0.0.0.0:443:443"
|
|
261
|
+
# volumes:
|
|
262
|
+
# - ./Caddyfile:/etc/caddy/Caddyfile
|
|
263
|
+
# - caddy_data:/data
|
|
264
|
+
# depends_on:
|
|
265
|
+
# - web
|
|
266
|
+
# - server
|
|
267
|
+
# networks:
|
|
268
|
+
# - openfactu_net
|
|
269
|
+
#
|
|
270
|
+
# volumes:
|
|
271
|
+
# caddy_data:
|
|
272
|
+
#
|
|
273
|
+
# Caddyfile:
|
|
274
|
+
# ${host} {
|
|
275
|
+
# handle /api/* {
|
|
276
|
+
# reverse_proxy server:3000
|
|
277
|
+
# }
|
|
278
|
+
# handle {
|
|
279
|
+
# reverse_proxy web:80
|
|
280
|
+
# }
|
|
281
|
+
# }
|
|
282
|
+
`;
|
|
283
|
+
}
|
|
284
|
+
fs_1.default.writeFileSync(prodComposePath, composeContent);
|
|
285
|
+
prodSpinner.succeed('docker-compose.prod.yml generado');
|
|
286
|
+
// 6. Preguntar si levantar
|
|
287
|
+
logger_1.log.blank();
|
|
288
|
+
const { start } = await inquirer_1.default.prompt([
|
|
289
|
+
{
|
|
290
|
+
type: 'confirm',
|
|
291
|
+
name: 'start',
|
|
292
|
+
message: '¿Levantar los servicios ahora?',
|
|
293
|
+
default: true,
|
|
294
|
+
},
|
|
295
|
+
]);
|
|
296
|
+
if (start) {
|
|
297
|
+
const startSpinner = (0, ora_1.default)('Construyendo y levantando servicios...').start();
|
|
298
|
+
try {
|
|
299
|
+
(0, child_process_1.execSync)('docker compose -f docker-compose.prod.yml up -d --build', {
|
|
300
|
+
cwd: root,
|
|
301
|
+
stdio: 'pipe',
|
|
302
|
+
timeout: 300000,
|
|
303
|
+
});
|
|
304
|
+
startSpinner.succeed('Servicios levantados');
|
|
305
|
+
}
|
|
306
|
+
catch (err) {
|
|
307
|
+
startSpinner.fail('Error: ' + err.message);
|
|
308
|
+
logger_1.log.dim(' Ejecuta manualmente:');
|
|
309
|
+
logger_1.log.dim(` cd ${root} && docker compose -f docker-compose.prod.yml up -d --build`);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
logger_1.log.blank();
|
|
313
|
+
console.log(chalk_1.default.bold.green(' Despliegue configurado'));
|
|
314
|
+
console.log(chalk_1.default.dim(' ────────────────────────────────────'));
|
|
315
|
+
console.log(` ${chalk_1.default.dim('Web:')} ${chalk_1.default.cyan(webUrl)}`);
|
|
316
|
+
console.log(` ${chalk_1.default.dim('API:')} ${chalk_1.default.cyan(apiUrl)}`);
|
|
317
|
+
logger_1.log.blank();
|
|
318
|
+
if (mode === 'lan') {
|
|
319
|
+
logger_1.log.info('Accede desde otros equipos de la red con la URL de arriba');
|
|
320
|
+
}
|
|
321
|
+
else if (mode === 'public') {
|
|
322
|
+
logger_1.log.info('Asegúrate de que los puertos estén abiertos en el firewall');
|
|
323
|
+
if (useSSL) {
|
|
324
|
+
logger_1.log.info('Configura el reverse proxy (Caddy/Nginx) para SSL');
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
logger_1.log.blank();
|
|
328
|
+
logger_1.log.dim(' Comandos útiles:');
|
|
329
|
+
logger_1.log.dim(` docker compose -f docker-compose.prod.yml logs -f — Ver logs`);
|
|
330
|
+
logger_1.log.dim(` docker compose -f docker-compose.prod.yml down — Parar`);
|
|
331
|
+
logger_1.log.dim(` docker compose -f docker-compose.prod.yml restart — Reiniciar`);
|
|
332
|
+
logger_1.log.blank();
|
|
333
|
+
}
|
|
334
|
+
catch (err) {
|
|
335
|
+
logger_1.log.error(err.message);
|
|
336
|
+
process.exitCode = 1;
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
// ── openfactu deploy:status ──
|
|
340
|
+
program
|
|
341
|
+
.command('deploy:status')
|
|
342
|
+
.description('Muestra el estado de los servicios Docker')
|
|
343
|
+
.action(async () => {
|
|
344
|
+
try {
|
|
345
|
+
const root = (0, paths_1.getProjectRoot)();
|
|
346
|
+
const prodCompose = path_1.default.join(root, 'docker-compose.prod.yml');
|
|
347
|
+
const composeFile = fs_1.default.existsSync(prodCompose) ? 'docker-compose.prod.yml' : 'docker-compose.yml';
|
|
348
|
+
logger_1.log.info(`Usando: ${chalk_1.default.dim(composeFile)}`);
|
|
349
|
+
logger_1.log.blank();
|
|
350
|
+
const output = (0, child_process_1.execSync)(`docker compose -f ${composeFile} ps`, {
|
|
351
|
+
cwd: root,
|
|
352
|
+
}).toString();
|
|
353
|
+
console.log(output);
|
|
354
|
+
// Mostrar URLs
|
|
355
|
+
const envPath = path_1.default.join(root, '.env');
|
|
356
|
+
const env = readEnv(envPath);
|
|
357
|
+
const host = env.HOST || 'localhost';
|
|
358
|
+
const webPort = env.WEB_PORT || '8080';
|
|
359
|
+
const serverPort = env.SERVER_PORT || '3000';
|
|
360
|
+
const protocol = env.VITE_API_URL?.startsWith('https') ? 'https' : 'http';
|
|
361
|
+
logger_1.log.blank();
|
|
362
|
+
logger_1.log.info(`Web: ${chalk_1.default.cyan(`${protocol}://${host}:${webPort}`)}`);
|
|
363
|
+
logger_1.log.info(`API: ${chalk_1.default.cyan(`${protocol}://${host}:${serverPort}`)}`);
|
|
364
|
+
}
|
|
365
|
+
catch (err) {
|
|
366
|
+
logger_1.log.error('Docker no disponible o servicios no levantados');
|
|
367
|
+
logger_1.log.dim(' ' + err.message);
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
}
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.registerInstallCommand = registerInstallCommand;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const ora_1 = __importDefault(require("ora"));
|
|
9
|
+
const inquirer_1 = __importDefault(require("inquirer"));
|
|
10
|
+
const child_process_1 = require("child_process");
|
|
11
|
+
const https_1 = __importDefault(require("https"));
|
|
12
|
+
const os_1 = __importDefault(require("os"));
|
|
13
|
+
const fs_1 = __importDefault(require("fs"));
|
|
14
|
+
const path_1 = __importDefault(require("path"));
|
|
15
|
+
const logger_1 = require("../utils/logger");
|
|
16
|
+
const REPO_URL = 'https://github.com/AngelAcedo12/OpenFactu.git';
|
|
17
|
+
const GITHUB_OWNER = 'AngelAcedo12';
|
|
18
|
+
const GITHUB_REPO = 'OpenFactu';
|
|
19
|
+
function fetchJSON(url) {
|
|
20
|
+
return new Promise((resolve, reject) => {
|
|
21
|
+
https_1.default.get(url, { headers: { 'User-Agent': 'openfactu-cli' } }, (res) => {
|
|
22
|
+
let data = '';
|
|
23
|
+
res.on('data', (chunk) => (data += chunk));
|
|
24
|
+
res.on('end', () => {
|
|
25
|
+
try {
|
|
26
|
+
resolve(JSON.parse(data));
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
reject(new Error('Respuesta no es JSON'));
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
}).on('error', reject);
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
async function getGithubReleases() {
|
|
36
|
+
try {
|
|
37
|
+
const data = await fetchJSON(`https://api.github.com/repos/${GITHUB_OWNER}/${GITHUB_REPO}/releases`);
|
|
38
|
+
if (!Array.isArray(data))
|
|
39
|
+
return [];
|
|
40
|
+
return data.filter((r) => !r.draft);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function getAvailableBranches() {
|
|
47
|
+
try {
|
|
48
|
+
const output = (0, child_process_1.execSync)(`git ls-remote --heads ${REPO_URL}`, {
|
|
49
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
50
|
+
timeout: 15000,
|
|
51
|
+
}).toString().trim();
|
|
52
|
+
if (!output)
|
|
53
|
+
return [];
|
|
54
|
+
return output
|
|
55
|
+
.split('\n')
|
|
56
|
+
.map((line) => {
|
|
57
|
+
const match = line.match(/refs\/heads\/(.+)$/);
|
|
58
|
+
return match ? match[1] : null;
|
|
59
|
+
})
|
|
60
|
+
.filter((b) => b !== null);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return [];
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function checkDocker() {
|
|
67
|
+
try {
|
|
68
|
+
(0, child_process_1.execSync)('docker --version', { stdio: 'pipe' });
|
|
69
|
+
(0, child_process_1.execSync)('docker compose version', { stdio: 'pipe' });
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function registerInstallCommand(program) {
|
|
77
|
+
program
|
|
78
|
+
.command('install [directory]')
|
|
79
|
+
.description('Descarga e instala OpenFactu en un directorio')
|
|
80
|
+
.option('-t, --tag <tag>', 'Versión/tag específico (ej: v1.0.0)')
|
|
81
|
+
.option('-b, --branch <branch>', 'Branch específica (default: main)')
|
|
82
|
+
.option('--repo <url>', 'URL del repositorio', REPO_URL)
|
|
83
|
+
.option('--skip-deps', 'No instalar dependencias (npm install)')
|
|
84
|
+
.action(async (directory, opts) => {
|
|
85
|
+
console.log();
|
|
86
|
+
console.log(chalk_1.default.bold.white(' OpenFactu — Instalación'));
|
|
87
|
+
console.log(chalk_1.default.dim(' ────────────────────────────────────'));
|
|
88
|
+
console.log();
|
|
89
|
+
try {
|
|
90
|
+
const repoUrl = opts.repo;
|
|
91
|
+
// 1. Obtener releases de GitHub
|
|
92
|
+
const fetchSpinner = (0, ora_1.default)('Consultando releases en GitHub...').start();
|
|
93
|
+
const releases = await getGithubReleases();
|
|
94
|
+
const branches = getAvailableBranches();
|
|
95
|
+
fetchSpinner.succeed(`${releases.length} release(s), ${branches.length} branches disponibles`);
|
|
96
|
+
// 2. Elegir versión
|
|
97
|
+
let ref;
|
|
98
|
+
if (opts.tag) {
|
|
99
|
+
ref = opts.tag;
|
|
100
|
+
}
|
|
101
|
+
else if (opts.branch) {
|
|
102
|
+
ref = opts.branch;
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
// Menú interactivo
|
|
106
|
+
const choices = [];
|
|
107
|
+
// Releases estables primero
|
|
108
|
+
const stable = releases.filter((r) => !r.prerelease);
|
|
109
|
+
const prerelease = releases.filter((r) => r.prerelease);
|
|
110
|
+
for (const rel of stable.slice(0, 10)) {
|
|
111
|
+
const date = new Date(rel.published_at).toLocaleDateString('es-ES');
|
|
112
|
+
choices.push({
|
|
113
|
+
name: `${chalk_1.default.green(rel.tag_name)} ${chalk_1.default.white(rel.name || '')} ${chalk_1.default.dim(`(${date})`)}`,
|
|
114
|
+
value: rel.tag_name,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
if (prerelease.length > 0) {
|
|
118
|
+
choices.push(new inquirer_1.default.Separator(chalk_1.default.dim('── Pre-releases ──')));
|
|
119
|
+
for (const rel of prerelease.slice(0, 5)) {
|
|
120
|
+
const date = new Date(rel.published_at).toLocaleDateString('es-ES');
|
|
121
|
+
choices.push({
|
|
122
|
+
name: `${chalk_1.default.yellow(rel.tag_name)} ${chalk_1.default.white(rel.name || '')} ${chalk_1.default.dim(`(${date}) pre-release`)}`,
|
|
123
|
+
value: rel.tag_name,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (branches.length > 0) {
|
|
128
|
+
choices.push(new inquirer_1.default.Separator(chalk_1.default.dim('── Branches ──')));
|
|
129
|
+
for (const branch of branches) {
|
|
130
|
+
const label = branch === 'main' ? chalk_1.default.dim('(última versión)') : '';
|
|
131
|
+
choices.push({
|
|
132
|
+
name: `${chalk_1.default.cyan(branch)} ${label}`,
|
|
133
|
+
value: branch,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (choices.length === 0) {
|
|
138
|
+
choices.push({ name: 'main (default)', value: 'main' });
|
|
139
|
+
}
|
|
140
|
+
const { selected } = await inquirer_1.default.prompt([
|
|
141
|
+
{
|
|
142
|
+
type: 'list',
|
|
143
|
+
name: 'selected',
|
|
144
|
+
message: 'Selecciona la versión a instalar:',
|
|
145
|
+
choices,
|
|
146
|
+
pageSize: 15,
|
|
147
|
+
},
|
|
148
|
+
]);
|
|
149
|
+
ref = selected;
|
|
150
|
+
}
|
|
151
|
+
logger_1.log.info(`Versión seleccionada: ${chalk_1.default.cyan(ref)}`);
|
|
152
|
+
// 3. Directorio destino
|
|
153
|
+
let targetDir = directory;
|
|
154
|
+
if (!targetDir) {
|
|
155
|
+
const { dir } = await inquirer_1.default.prompt([
|
|
156
|
+
{
|
|
157
|
+
type: 'input',
|
|
158
|
+
name: 'dir',
|
|
159
|
+
message: 'Directorio de instalación:',
|
|
160
|
+
default: path_1.default.join(os_1.default.homedir(), 'openfactu'),
|
|
161
|
+
},
|
|
162
|
+
]);
|
|
163
|
+
targetDir = dir;
|
|
164
|
+
}
|
|
165
|
+
targetDir = path_1.default.resolve(targetDir);
|
|
166
|
+
// Verificar si el directorio ya existe
|
|
167
|
+
if (fs_1.default.existsSync(targetDir)) {
|
|
168
|
+
const contents = fs_1.default.readdirSync(targetDir);
|
|
169
|
+
if (contents.length > 0) {
|
|
170
|
+
const { overwrite } = await inquirer_1.default.prompt([
|
|
171
|
+
{
|
|
172
|
+
type: 'confirm',
|
|
173
|
+
name: 'overwrite',
|
|
174
|
+
message: `El directorio ${targetDir} no está vacío. ¿Continuar?`,
|
|
175
|
+
default: false,
|
|
176
|
+
},
|
|
177
|
+
]);
|
|
178
|
+
if (!overwrite) {
|
|
179
|
+
logger_1.log.info('Instalación cancelada');
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
logger_1.log.info(`Directorio: ${chalk_1.default.dim(targetDir)}`);
|
|
185
|
+
logger_1.log.blank();
|
|
186
|
+
// 4. Crear directorio si no existe (con sudo si hace falta)
|
|
187
|
+
if (!fs_1.default.existsSync(targetDir)) {
|
|
188
|
+
try {
|
|
189
|
+
fs_1.default.mkdirSync(targetDir, { recursive: true });
|
|
190
|
+
}
|
|
191
|
+
catch (mkdirErr) {
|
|
192
|
+
if (mkdirErr.code === 'EACCES') {
|
|
193
|
+
logger_1.log.warn('Sin permisos. Creando directorio con sudo...');
|
|
194
|
+
try {
|
|
195
|
+
const user = process.env.USER || process.env.USERNAME || 'root';
|
|
196
|
+
(0, child_process_1.execSync)(`sudo mkdir -p "${targetDir}" && sudo chown -R ${user}:${user} "${targetDir}"`, {
|
|
197
|
+
stdio: 'inherit',
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
catch {
|
|
201
|
+
logger_1.log.error(`No se pudo crear ${targetDir}. Ejecuta con sudo o elige otro directorio.`);
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
throw mkdirErr;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
// 5. Clonar repositorio
|
|
211
|
+
const cloneSpinner = (0, ora_1.default)('Descargando OpenFactu...').start();
|
|
212
|
+
const isTag = releases.some((r) => r.tag_name === ref);
|
|
213
|
+
const cloneCmd = isTag
|
|
214
|
+
? `git clone --depth 1 --branch ${ref} ${repoUrl} "${targetDir}"`
|
|
215
|
+
: `git clone --branch ${ref} ${repoUrl} "${targetDir}"`;
|
|
216
|
+
try {
|
|
217
|
+
(0, child_process_1.execSync)(cloneCmd, { stdio: 'pipe', timeout: 120000 });
|
|
218
|
+
cloneSpinner.succeed('Código descargado');
|
|
219
|
+
}
|
|
220
|
+
catch (err) {
|
|
221
|
+
// Fallback: clonar todo y checkout
|
|
222
|
+
try {
|
|
223
|
+
cloneSpinner.text = 'Descargando (método alternativo)...';
|
|
224
|
+
(0, child_process_1.execSync)(`git clone ${repoUrl} "${targetDir}"`, { stdio: 'pipe', timeout: 180000 });
|
|
225
|
+
(0, child_process_1.execSync)(`git checkout ${ref}`, { cwd: targetDir, stdio: 'pipe' });
|
|
226
|
+
cloneSpinner.succeed('Código descargado');
|
|
227
|
+
}
|
|
228
|
+
catch (err2) {
|
|
229
|
+
cloneSpinner.fail('Error al descargar: ' + err2.message);
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
// 6. Copiar .env.example a .env
|
|
234
|
+
const envExample = path_1.default.join(targetDir, '.env.example');
|
|
235
|
+
const envFile = path_1.default.join(targetDir, '.env');
|
|
236
|
+
if (fs_1.default.existsSync(envExample) && !fs_1.default.existsSync(envFile)) {
|
|
237
|
+
fs_1.default.copyFileSync(envExample, envFile);
|
|
238
|
+
logger_1.log.success('Archivo .env creado desde .env.example');
|
|
239
|
+
}
|
|
240
|
+
// 7. Preguntar modo de instalación
|
|
241
|
+
const hasDocker = checkDocker();
|
|
242
|
+
if (!hasDocker) {
|
|
243
|
+
logger_1.log.warn('Docker no detectado. OpenFactu requiere Docker para funcionar.');
|
|
244
|
+
logger_1.log.dim(' Instala Docker Desktop: https://docs.docker.com/get-docker/');
|
|
245
|
+
logger_1.log.blank();
|
|
246
|
+
}
|
|
247
|
+
const { installMode } = await inquirer_1.default.prompt([
|
|
248
|
+
{
|
|
249
|
+
type: 'list',
|
|
250
|
+
name: 'installMode',
|
|
251
|
+
message: 'Modo de instalación:',
|
|
252
|
+
choices: [
|
|
253
|
+
...(hasDocker ? [{
|
|
254
|
+
name: `${chalk_1.default.green('Docker')} ${chalk_1.default.dim('— recomendado, funciona en Windows/Mac/Linux')}`,
|
|
255
|
+
value: 'docker',
|
|
256
|
+
}] : []),
|
|
257
|
+
{
|
|
258
|
+
name: `${chalk_1.default.dim('Solo descargar')} ${chalk_1.default.dim('— instalar dependencias manualmente después')}`,
|
|
259
|
+
value: 'none',
|
|
260
|
+
},
|
|
261
|
+
],
|
|
262
|
+
},
|
|
263
|
+
]);
|
|
264
|
+
if (installMode === 'docker') {
|
|
265
|
+
// Docker: build + up
|
|
266
|
+
const dockerSpinner = (0, ora_1.default)('Construyendo contenedores Docker...').start();
|
|
267
|
+
try {
|
|
268
|
+
(0, child_process_1.execSync)('docker compose build', { cwd: targetDir, stdio: 'pipe', timeout: 300000 });
|
|
269
|
+
dockerSpinner.succeed('Contenedores construidos');
|
|
270
|
+
const { startNow } = await inquirer_1.default.prompt([
|
|
271
|
+
{ type: 'confirm', name: 'startNow', message: '¿Arrancar los servicios?', default: true },
|
|
272
|
+
]);
|
|
273
|
+
if (startNow) {
|
|
274
|
+
const upSpinner = (0, ora_1.default)('Levantando servicios...').start();
|
|
275
|
+
(0, child_process_1.execSync)('docker compose up -d', { cwd: targetDir, stdio: 'pipe', timeout: 120000 });
|
|
276
|
+
upSpinner.succeed('Servicios levantados');
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
catch (err) {
|
|
280
|
+
dockerSpinner.fail('Error Docker: ' + err.message);
|
|
281
|
+
logger_1.log.dim(` Ejecuta manualmente: cd ${targetDir} && docker compose up -d`);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
// 7. Resumen
|
|
285
|
+
const installedPkg = path_1.default.join(targetDir, 'package.json');
|
|
286
|
+
let installedVersion = '?';
|
|
287
|
+
if (fs_1.default.existsSync(installedPkg)) {
|
|
288
|
+
try {
|
|
289
|
+
const pkg = JSON.parse(fs_1.default.readFileSync(installedPkg, 'utf-8'));
|
|
290
|
+
installedVersion = pkg.version || '?';
|
|
291
|
+
}
|
|
292
|
+
catch { }
|
|
293
|
+
}
|
|
294
|
+
let installedCommit = '?';
|
|
295
|
+
try {
|
|
296
|
+
installedCommit = (0, child_process_1.execSync)('git rev-parse --short HEAD', { cwd: targetDir }).toString().trim();
|
|
297
|
+
}
|
|
298
|
+
catch { }
|
|
299
|
+
logger_1.log.blank();
|
|
300
|
+
console.log(chalk_1.default.bold.green(' Instalación completada'));
|
|
301
|
+
console.log(chalk_1.default.dim(' ────────────────────────────────────'));
|
|
302
|
+
console.log(` ${chalk_1.default.dim('Versión:')} ${chalk_1.default.cyan(installedVersion)}`);
|
|
303
|
+
console.log(` ${chalk_1.default.dim('Ref:')} ${chalk_1.default.cyan(ref)}`);
|
|
304
|
+
console.log(` ${chalk_1.default.dim('Commit:')} ${chalk_1.default.cyan(installedCommit)}`);
|
|
305
|
+
console.log(` ${chalk_1.default.dim('Directorio:')} ${chalk_1.default.white(targetDir)}`);
|
|
306
|
+
console.log(` ${chalk_1.default.dim('Modo:')} ${chalk_1.default.white(installMode)}`);
|
|
307
|
+
logger_1.log.blank();
|
|
308
|
+
logger_1.log.dim(' Próximos pasos:');
|
|
309
|
+
logger_1.log.dim(` cd ${targetDir}`);
|
|
310
|
+
if (installMode === 'docker') {
|
|
311
|
+
logger_1.log.dim(' openfactu deploy — Configurar acceso externo');
|
|
312
|
+
logger_1.log.dim(' openfactu deploy:status — Ver estado de servicios');
|
|
313
|
+
}
|
|
314
|
+
else {
|
|
315
|
+
logger_1.log.dim(' docker compose up -d — Levantar con Docker');
|
|
316
|
+
logger_1.log.dim(' openfactu deploy — Configurar acceso externo');
|
|
317
|
+
}
|
|
318
|
+
logger_1.log.blank();
|
|
319
|
+
}
|
|
320
|
+
catch (err) {
|
|
321
|
+
logger_1.log.error(err.message);
|
|
322
|
+
process.exitCode = 1;
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
}
|
|
@@ -11,11 +11,12 @@ const path_1 = __importDefault(require("path"));
|
|
|
11
11
|
const fs_1 = __importDefault(require("fs"));
|
|
12
12
|
const db_1 = require("../utils/db");
|
|
13
13
|
const logger_1 = require("../utils/logger");
|
|
14
|
-
const
|
|
14
|
+
const paths_1 = require("../utils/paths");
|
|
15
|
+
function getMigrDir() { return (0, paths_1.getMigrationsDir)(); }
|
|
15
16
|
function getMigrationFiles() {
|
|
16
|
-
if (!fs_1.default.existsSync(
|
|
17
|
+
if (!fs_1.default.existsSync(getMigrDir()))
|
|
17
18
|
return [];
|
|
18
|
-
return fs_1.default.readdirSync(
|
|
19
|
+
return fs_1.default.readdirSync(getMigrDir()).filter((f) => f.endsWith('.sql')).sort();
|
|
19
20
|
}
|
|
20
21
|
async function getAppliedMigrations(tenantDb, schemaName) {
|
|
21
22
|
try {
|
|
@@ -28,7 +29,7 @@ async function getAppliedMigrations(tenantDb, schemaName) {
|
|
|
28
29
|
}
|
|
29
30
|
async function applyMigration(tenantDb, schemaName, file) {
|
|
30
31
|
const migrationId = file.replace('.sql', '');
|
|
31
|
-
const filePath = path_1.default.join(
|
|
32
|
+
const filePath = path_1.default.join(getMigrDir(), file);
|
|
32
33
|
let rawSql = fs_1.default.readFileSync(filePath, 'utf8');
|
|
33
34
|
const processedSql = rawSql.replace(/{{schema}}/g, schemaName);
|
|
34
35
|
const statements = processedSql
|
|
@@ -12,7 +12,6 @@ const path_1 = __importDefault(require("path"));
|
|
|
12
12
|
const db_1 = require("../utils/db");
|
|
13
13
|
const logger_1 = require("../utils/logger");
|
|
14
14
|
const paths_1 = require("../utils/paths");
|
|
15
|
-
const PLUGINS_DIR = (0, paths_1.getPluginsDir)();
|
|
16
15
|
function registerPluginCommand(program) {
|
|
17
16
|
const plugin = program
|
|
18
17
|
.command('plugin')
|
|
@@ -26,8 +25,8 @@ function registerPluginCommand(program) {
|
|
|
26
25
|
try {
|
|
27
26
|
// Leer plugins del filesystem
|
|
28
27
|
const installed = [];
|
|
29
|
-
if (fs_1.default.existsSync(
|
|
30
|
-
const dirs = fs_1.default.readdirSync(
|
|
28
|
+
if (fs_1.default.existsSync((0, paths_1.getPluginsDir)())) {
|
|
29
|
+
const dirs = fs_1.default.readdirSync((0, paths_1.getPluginsDir)()).filter((d) => fs_1.default.statSync(path_1.default.join((0, paths_1.getPluginsDir)(), d)).isDirectory());
|
|
31
30
|
installed.push(...dirs);
|
|
32
31
|
}
|
|
33
32
|
if (installed.length === 0) {
|
|
@@ -38,7 +37,7 @@ function registerPluginCommand(program) {
|
|
|
38
37
|
const publicDb = (0, db_1.getPublicDb)();
|
|
39
38
|
let tenantPlugins = [];
|
|
40
39
|
try {
|
|
41
|
-
tenantPlugins = await publicDb.select().from(db_1.schema.tenantPlugins);
|
|
40
|
+
tenantPlugins = await publicDb.select().from((0, db_1.schema)().tenantPlugins);
|
|
42
41
|
}
|
|
43
42
|
catch {
|
|
44
43
|
// Tabla puede no existir aún
|
|
@@ -55,7 +54,7 @@ function registerPluginCommand(program) {
|
|
|
55
54
|
style: { head: [], border: ['dim'] },
|
|
56
55
|
});
|
|
57
56
|
for (const pluginId of installed) {
|
|
58
|
-
const manifestPath = path_1.default.join(
|
|
57
|
+
const manifestPath = path_1.default.join((0, paths_1.getPluginsDir)(), pluginId, 'manifest.json');
|
|
59
58
|
const hasManifest = fs_1.default.existsSync(manifestPath);
|
|
60
59
|
let manifest = null;
|
|
61
60
|
if (hasManifest) {
|
|
@@ -50,8 +50,8 @@ function registerSetupCommand(program) {
|
|
|
50
50
|
const adminSpinner = (0, ora_1.default)('Verificando usuario administrador...').start();
|
|
51
51
|
const [existingAdmin] = await publicDb
|
|
52
52
|
.select()
|
|
53
|
-
.from(db_1.schema.globalUsers)
|
|
54
|
-
.where((0, db_1.eq)(db_1.schema.globalUsers.username, 'admin'));
|
|
53
|
+
.from((0, db_1.schema)().globalUsers)
|
|
54
|
+
.where((0, db_1.eq)((0, db_1.schema)().globalUsers.username, 'admin'));
|
|
55
55
|
if (existingAdmin) {
|
|
56
56
|
adminSpinner.succeed('Usuario admin ya existe');
|
|
57
57
|
}
|
|
@@ -67,7 +67,7 @@ function registerSetupCommand(program) {
|
|
|
67
67
|
},
|
|
68
68
|
]);
|
|
69
69
|
const hashedPassword = await bcrypt_1.default.hash(adminPassword, 10);
|
|
70
|
-
await publicDb.insert(db_1.schema.globalUsers).values({
|
|
70
|
+
await publicDb.insert((0, db_1.schema)().globalUsers).values({
|
|
71
71
|
id: crypto_1.default.randomUUID(),
|
|
72
72
|
email: 'admin@openfactu.com',
|
|
73
73
|
username: 'admin',
|
|
@@ -77,7 +77,7 @@ function registerSetupCommand(program) {
|
|
|
77
77
|
adminSpinner.succeed('Usuario admin creado (admin@openfactu.com)');
|
|
78
78
|
}
|
|
79
79
|
// 4. Verificar/crear primer tenant
|
|
80
|
-
const tenants = await publicDb.select().from(db_1.schema.tenants);
|
|
80
|
+
const tenants = await publicDb.select().from((0, db_1.schema)().tenants);
|
|
81
81
|
if (tenants.length > 0) {
|
|
82
82
|
logger_1.log.success(`${tenants.length} tenant(s) ya existen`);
|
|
83
83
|
}
|
|
@@ -106,7 +106,7 @@ function registerSetupCommand(program) {
|
|
|
106
106
|
.replace(/^_|_$/g, '');
|
|
107
107
|
const tenantSpinner = (0, ora_1.default)(`Creando empresa "${tenantName}"...`).start();
|
|
108
108
|
const tenantId = crypto_1.default.randomUUID();
|
|
109
|
-
await publicDb.insert(db_1.schema.tenants).values({
|
|
109
|
+
await publicDb.insert((0, db_1.schema)().tenants).values({
|
|
110
110
|
id: tenantId,
|
|
111
111
|
name: tenantName,
|
|
112
112
|
schemaName,
|
|
@@ -118,10 +118,10 @@ function registerSetupCommand(program) {
|
|
|
118
118
|
// Asignar admin al tenant
|
|
119
119
|
const [admin] = await publicDb
|
|
120
120
|
.select()
|
|
121
|
-
.from(db_1.schema.globalUsers)
|
|
122
|
-
.where((0, db_1.eq)(db_1.schema.globalUsers.username, 'admin'));
|
|
121
|
+
.from((0, db_1.schema)().globalUsers)
|
|
122
|
+
.where((0, db_1.eq)((0, db_1.schema)().globalUsers.username, 'admin'));
|
|
123
123
|
if (admin) {
|
|
124
|
-
await publicDb.insert(db_1.schema.userTenantMemberships).values({
|
|
124
|
+
await publicDb.insert((0, db_1.schema)().userTenantMemberships).values({
|
|
125
125
|
id: crypto_1.default.randomUUID(),
|
|
126
126
|
userId: admin.id,
|
|
127
127
|
tenantId,
|
|
@@ -80,15 +80,15 @@ function registerTenantCommand(program) {
|
|
|
80
80
|
// Verificar que no exista
|
|
81
81
|
const [existing] = await publicDb
|
|
82
82
|
.select()
|
|
83
|
-
.from(db_1.schema.tenants)
|
|
84
|
-
.where((0, db_1.eq)(db_1.schema.tenants.name, tenantName));
|
|
83
|
+
.from((0, db_1.schema)().tenants)
|
|
84
|
+
.where((0, db_1.eq)((0, db_1.schema)().tenants.name, tenantName));
|
|
85
85
|
if (existing) {
|
|
86
86
|
spinner.warn(`El tenant "${tenantName}" ya existe (${existing.id})`);
|
|
87
87
|
return;
|
|
88
88
|
}
|
|
89
89
|
// Crear registro en la tabla Tenant
|
|
90
90
|
const tenantId = crypto_1.default.randomUUID();
|
|
91
|
-
await publicDb.insert(db_1.schema.tenants).values({
|
|
91
|
+
await publicDb.insert((0, db_1.schema)().tenants).values({
|
|
92
92
|
id: tenantId,
|
|
93
93
|
name: tenantName,
|
|
94
94
|
schemaName,
|
|
@@ -12,12 +12,12 @@ const fs_1 = __importDefault(require("fs"));
|
|
|
12
12
|
const path_1 = __importDefault(require("path"));
|
|
13
13
|
const logger_1 = require("../utils/logger");
|
|
14
14
|
const paths_1 = require("../utils/paths");
|
|
15
|
-
|
|
15
|
+
function ROOT_DIR() { return (0, paths_1.getProjectRoot)(); }
|
|
16
16
|
const BACKUP_DIRS = ['storage', 'plugins', '.env'];
|
|
17
17
|
const SAFE_DIRS = ['storage', 'plugins', 'node_modules', '.env', '.git'];
|
|
18
18
|
function getCurrentVersion() {
|
|
19
19
|
try {
|
|
20
|
-
const pkg = JSON.parse(fs_1.default.readFileSync(path_1.default.join(ROOT_DIR, 'package.json'), 'utf-8'));
|
|
20
|
+
const pkg = JSON.parse(fs_1.default.readFileSync(path_1.default.join(ROOT_DIR(), 'package.json'), 'utf-8'));
|
|
21
21
|
return pkg.version || '0.0.0';
|
|
22
22
|
}
|
|
23
23
|
catch {
|
|
@@ -26,7 +26,7 @@ function getCurrentVersion() {
|
|
|
26
26
|
}
|
|
27
27
|
function getCurrentBranch() {
|
|
28
28
|
try {
|
|
29
|
-
return (0, child_process_1.execSync)('git rev-parse --abbrev-ref HEAD', { cwd: ROOT_DIR }).toString().trim();
|
|
29
|
+
return (0, child_process_1.execSync)('git rev-parse --abbrev-ref HEAD', { cwd: ROOT_DIR() }).toString().trim();
|
|
30
30
|
}
|
|
31
31
|
catch {
|
|
32
32
|
return 'unknown';
|
|
@@ -34,7 +34,7 @@ function getCurrentBranch() {
|
|
|
34
34
|
}
|
|
35
35
|
function getCurrentCommit() {
|
|
36
36
|
try {
|
|
37
|
-
return (0, child_process_1.execSync)('git rev-parse --short HEAD', { cwd: ROOT_DIR }).toString().trim();
|
|
37
|
+
return (0, child_process_1.execSync)('git rev-parse --short HEAD', { cwd: ROOT_DIR() }).toString().trim();
|
|
38
38
|
}
|
|
39
39
|
catch {
|
|
40
40
|
return 'unknown';
|
|
@@ -42,7 +42,7 @@ function getCurrentCommit() {
|
|
|
42
42
|
}
|
|
43
43
|
function hasUncommittedChanges() {
|
|
44
44
|
try {
|
|
45
|
-
const output = (0, child_process_1.execSync)('git status --porcelain', { cwd: ROOT_DIR }).toString().trim();
|
|
45
|
+
const output = (0, child_process_1.execSync)('git status --porcelain', { cwd: ROOT_DIR() }).toString().trim();
|
|
46
46
|
return output.length > 0;
|
|
47
47
|
}
|
|
48
48
|
catch {
|
|
@@ -51,8 +51,8 @@ function hasUncommittedChanges() {
|
|
|
51
51
|
}
|
|
52
52
|
function getRemoteTags() {
|
|
53
53
|
try {
|
|
54
|
-
(0, child_process_1.execSync)('git fetch --tags', { cwd: ROOT_DIR, stdio: 'pipe' });
|
|
55
|
-
const output = (0, child_process_1.execSync)('git tag --list "v*" --sort=-version:refname', { cwd: ROOT_DIR }).toString().trim();
|
|
54
|
+
(0, child_process_1.execSync)('git fetch --tags', { cwd: ROOT_DIR(), stdio: 'pipe' });
|
|
55
|
+
const output = (0, child_process_1.execSync)('git tag --list "v*" --sort=-version:refname', { cwd: ROOT_DIR() }).toString().trim();
|
|
56
56
|
return output ? output.split('\n') : [];
|
|
57
57
|
}
|
|
58
58
|
catch {
|
|
@@ -61,8 +61,8 @@ function getRemoteTags() {
|
|
|
61
61
|
}
|
|
62
62
|
function getRemoteLatestCommit(branch) {
|
|
63
63
|
try {
|
|
64
|
-
(0, child_process_1.execSync)('git fetch origin', { cwd: ROOT_DIR, stdio: 'pipe' });
|
|
65
|
-
return (0, child_process_1.execSync)(`git rev-parse --short origin/${branch}`, { cwd: ROOT_DIR }).toString().trim();
|
|
64
|
+
(0, child_process_1.execSync)('git fetch origin', { cwd: ROOT_DIR(), stdio: 'pipe' });
|
|
65
|
+
return (0, child_process_1.execSync)(`git rev-parse --short origin/${branch}`, { cwd: ROOT_DIR() }).toString().trim();
|
|
66
66
|
}
|
|
67
67
|
catch {
|
|
68
68
|
return 'unknown';
|
|
@@ -109,7 +109,7 @@ function registerUpdateCommand(program) {
|
|
|
109
109
|
// Guardar cambios locales
|
|
110
110
|
const stashSpinner = (0, ora_1.default)('Guardando cambios locales (git stash)...').start();
|
|
111
111
|
try {
|
|
112
|
-
(0, child_process_1.execSync)('git stash push -m "openfactu-cli-update-backup"', { cwd: ROOT_DIR, stdio: 'pipe' });
|
|
112
|
+
(0, child_process_1.execSync)('git stash push -m "openfactu-cli-update-backup"', { cwd: ROOT_DIR(), stdio: 'pipe' });
|
|
113
113
|
stashSpinner.succeed('Cambios locales guardados en stash');
|
|
114
114
|
}
|
|
115
115
|
catch (err) {
|
|
@@ -119,7 +119,7 @@ function registerUpdateCommand(program) {
|
|
|
119
119
|
// 2. Fetch remoto
|
|
120
120
|
const fetchSpinner = (0, ora_1.default)('Descargando información del repositorio...').start();
|
|
121
121
|
try {
|
|
122
|
-
(0, child_process_1.execSync)('git fetch --all --tags', { cwd: ROOT_DIR, stdio: 'pipe' });
|
|
122
|
+
(0, child_process_1.execSync)('git fetch --all --tags', { cwd: ROOT_DIR(), stdio: 'pipe' });
|
|
123
123
|
fetchSpinner.succeed('Repositorio actualizado');
|
|
124
124
|
}
|
|
125
125
|
catch (err) {
|
|
@@ -142,10 +142,10 @@ function registerUpdateCommand(program) {
|
|
|
142
142
|
}
|
|
143
143
|
// Mostrar commits pendientes
|
|
144
144
|
try {
|
|
145
|
-
const behindCount = (0, child_process_1.execSync)(`git rev-list --count HEAD..origin/${branch}`, { cwd: ROOT_DIR }).toString().trim();
|
|
145
|
+
const behindCount = (0, child_process_1.execSync)(`git rev-list --count HEAD..origin/${branch}`, { cwd: ROOT_DIR() }).toString().trim();
|
|
146
146
|
logger_1.log.info(`Commits nuevos disponibles: ${chalk_1.default.yellow(behindCount)}`);
|
|
147
147
|
// Mostrar resumen de cambios
|
|
148
|
-
const changelog = (0, child_process_1.execSync)(`git log --oneline HEAD..origin/${branch} --max-count=10`, { cwd: ROOT_DIR }).toString().trim();
|
|
148
|
+
const changelog = (0, child_process_1.execSync)(`git log --oneline HEAD..origin/${branch} --max-count=10`, { cwd: ROOT_DIR() }).toString().trim();
|
|
149
149
|
if (changelog) {
|
|
150
150
|
logger_1.log.blank();
|
|
151
151
|
logger_1.log.dim(' Cambios recientes:');
|
|
@@ -177,7 +177,7 @@ function registerUpdateCommand(program) {
|
|
|
177
177
|
const backupSpinner = (0, ora_1.default)('Verificando archivos protegidos...').start();
|
|
178
178
|
const protectedFiles = [];
|
|
179
179
|
for (const item of BACKUP_DIRS) {
|
|
180
|
-
const itemPath = path_1.default.join(ROOT_DIR, item);
|
|
180
|
+
const itemPath = path_1.default.join(ROOT_DIR(), item);
|
|
181
181
|
if (fs_1.default.existsSync(itemPath)) {
|
|
182
182
|
protectedFiles.push(item);
|
|
183
183
|
}
|
|
@@ -187,11 +187,11 @@ function registerUpdateCommand(program) {
|
|
|
187
187
|
const updateSpinner = (0, ora_1.default)('Aplicando actualización...').start();
|
|
188
188
|
try {
|
|
189
189
|
if (opts.tag) {
|
|
190
|
-
(0, child_process_1.execSync)(`git checkout ${opts.tag}`, { cwd: ROOT_DIR, stdio: 'pipe' });
|
|
190
|
+
(0, child_process_1.execSync)(`git checkout ${opts.tag}`, { cwd: ROOT_DIR(), stdio: 'pipe' });
|
|
191
191
|
}
|
|
192
192
|
else {
|
|
193
193
|
const branch = opts.branch || currentBranch || 'main';
|
|
194
|
-
(0, child_process_1.execSync)(`git pull origin ${branch} --ff-only`, { cwd: ROOT_DIR, stdio: 'pipe' });
|
|
194
|
+
(0, child_process_1.execSync)(`git pull origin ${branch} --ff-only`, { cwd: ROOT_DIR(), stdio: 'pipe' });
|
|
195
195
|
}
|
|
196
196
|
updateSpinner.succeed('Código actualizado');
|
|
197
197
|
}
|
|
@@ -200,7 +200,7 @@ function registerUpdateCommand(program) {
|
|
|
200
200
|
logger_1.log.warn('Intentando merge...');
|
|
201
201
|
try {
|
|
202
202
|
const branch = opts.branch || currentBranch || 'main';
|
|
203
|
-
(0, child_process_1.execSync)(`git pull origin ${branch}`, { cwd: ROOT_DIR, stdio: 'pipe' });
|
|
203
|
+
(0, child_process_1.execSync)(`git pull origin ${branch}`, { cwd: ROOT_DIR(), stdio: 'pipe' });
|
|
204
204
|
logger_1.log.success('Merge completado');
|
|
205
205
|
}
|
|
206
206
|
catch (mergeErr) {
|
|
@@ -213,7 +213,7 @@ function registerUpdateCommand(program) {
|
|
|
213
213
|
// 7. Instalar dependencias
|
|
214
214
|
const depsSpinner = (0, ora_1.default)('Instalando dependencias...').start();
|
|
215
215
|
try {
|
|
216
|
-
(0, child_process_1.execSync)('npm install', { cwd: ROOT_DIR, stdio: 'pipe', timeout: 120000 });
|
|
216
|
+
(0, child_process_1.execSync)('npm install', { cwd: ROOT_DIR(), stdio: 'pipe', timeout: 120000 });
|
|
217
217
|
depsSpinner.succeed('Dependencias instaladas');
|
|
218
218
|
}
|
|
219
219
|
catch (err) {
|
|
@@ -248,7 +248,7 @@ function registerUpdateCommand(program) {
|
|
|
248
248
|
const currentVersion = getCurrentVersion();
|
|
249
249
|
const currentBranch = getCurrentBranch();
|
|
250
250
|
const currentCommit = getCurrentCommit();
|
|
251
|
-
(0, child_process_1.execSync)('git fetch --all --tags', { cwd: ROOT_DIR, stdio: 'pipe' });
|
|
251
|
+
(0, child_process_1.execSync)('git fetch --all --tags', { cwd: ROOT_DIR(), stdio: 'pipe' });
|
|
252
252
|
const remoteCommit = getRemoteLatestCommit(currentBranch);
|
|
253
253
|
const tags = getRemoteTags();
|
|
254
254
|
spinner.stop();
|
|
@@ -258,7 +258,7 @@ function registerUpdateCommand(program) {
|
|
|
258
258
|
logger_1.log.success('Estás en la última versión');
|
|
259
259
|
}
|
|
260
260
|
else {
|
|
261
|
-
const behindCount = (0, child_process_1.execSync)(`git rev-list --count HEAD..origin/${currentBranch}`, { cwd: ROOT_DIR }).toString().trim();
|
|
261
|
+
const behindCount = (0, child_process_1.execSync)(`git rev-list --count HEAD..origin/${currentBranch}`, { cwd: ROOT_DIR() }).toString().trim();
|
|
262
262
|
logger_1.log.warn(`Hay ${chalk_1.default.yellow(behindCount)} commit(s) nuevos disponibles`);
|
|
263
263
|
logger_1.log.dim(` Ejecuta: openfactu update`);
|
|
264
264
|
}
|
package/dist/src/index.js
CHANGED
|
@@ -8,17 +8,21 @@ const tenant_1 = require("./commands/tenant");
|
|
|
8
8
|
const plugin_1 = require("./commands/plugin");
|
|
9
9
|
const setup_1 = require("./commands/setup");
|
|
10
10
|
const update_1 = require("./commands/update");
|
|
11
|
+
const install_1 = require("./commands/install");
|
|
12
|
+
const deploy_1 = require("./commands/deploy");
|
|
11
13
|
function createCLI() {
|
|
12
14
|
const program = new commander_1.Command();
|
|
13
15
|
program
|
|
14
16
|
.name('openfactu')
|
|
15
17
|
.description('CLI para gestionar OpenFactu')
|
|
16
|
-
.version('0.
|
|
18
|
+
.version('0.0.4');
|
|
17
19
|
(0, version_1.registerVersionCommand)(program);
|
|
18
20
|
(0, migrate_1.registerMigrateCommand)(program);
|
|
19
21
|
(0, tenant_1.registerTenantCommand)(program);
|
|
20
22
|
(0, plugin_1.registerPluginCommand)(program);
|
|
21
23
|
(0, setup_1.registerSetupCommand)(program);
|
|
22
24
|
(0, update_1.registerUpdateCommand)(program);
|
|
25
|
+
(0, install_1.registerInstallCommand)(program);
|
|
26
|
+
(0, deploy_1.registerDeployCommand)(program);
|
|
23
27
|
return program;
|
|
24
28
|
}
|
package/dist/src/utils/db.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Pool } from 'pg';
|
|
2
2
|
import { eq, sql } from 'drizzle-orm';
|
|
3
|
-
declare
|
|
3
|
+
declare function getSchema(): any;
|
|
4
4
|
export declare function getPublicDb(): any;
|
|
5
5
|
export declare function getTenantDb(schemaName: string): import("drizzle-orm/node-postgres").NodePgDatabase<any> & {
|
|
6
6
|
$client: Pool;
|
|
@@ -9,4 +9,5 @@ export declare function getAllTenants(): Promise<any>;
|
|
|
9
9
|
export declare function getTenantByName(name: string): Promise<any>;
|
|
10
10
|
export declare function testConnection(): Promise<boolean>;
|
|
11
11
|
export declare function disconnect(): Promise<void>;
|
|
12
|
-
export {
|
|
12
|
+
export { sql, eq };
|
|
13
|
+
export { getSchema as schema };
|
package/dist/src/utils/db.js
CHANGED
|
@@ -1,49 +1,59 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.eq = exports.sql =
|
|
6
|
+
exports.eq = exports.sql = void 0;
|
|
4
7
|
exports.getPublicDb = getPublicDb;
|
|
5
8
|
exports.getTenantDb = getTenantDb;
|
|
6
9
|
exports.getAllTenants = getAllTenants;
|
|
7
10
|
exports.getTenantByName = getTenantByName;
|
|
8
11
|
exports.testConnection = testConnection;
|
|
9
12
|
exports.disconnect = disconnect;
|
|
13
|
+
exports.schema = getSchema;
|
|
10
14
|
const node_postgres_1 = require("drizzle-orm/node-postgres");
|
|
11
15
|
const pg_1 = require("pg");
|
|
12
16
|
const drizzle_orm_1 = require("drizzle-orm");
|
|
13
17
|
Object.defineProperty(exports, "eq", { enumerable: true, get: function () { return drizzle_orm_1.eq; } });
|
|
14
18
|
Object.defineProperty(exports, "sql", { enumerable: true, get: function () { return drizzle_orm_1.sql; } });
|
|
19
|
+
const path_1 = __importDefault(require("path"));
|
|
15
20
|
const config_1 = require("./config");
|
|
16
|
-
// Importar schema de forma dinámica según la ruta del proyecto
|
|
17
21
|
const paths_1 = require("./paths");
|
|
18
|
-
const schemaPath = require('path').join((0, paths_1.getServerSrcDir)(), 'db/schema');
|
|
19
|
-
const schema = require(schemaPath);
|
|
20
|
-
exports.schema = schema;
|
|
21
22
|
let pool = null;
|
|
22
23
|
let db = null;
|
|
24
|
+
let _schema = null;
|
|
25
|
+
function getSchema() {
|
|
26
|
+
if (!_schema) {
|
|
27
|
+
_schema = require(path_1.default.join((0, paths_1.getServerSrcDir)(), 'db/schema'));
|
|
28
|
+
}
|
|
29
|
+
return _schema;
|
|
30
|
+
}
|
|
23
31
|
function getPublicDb() {
|
|
24
32
|
if (db)
|
|
25
33
|
return db;
|
|
26
34
|
const config = (0, config_1.loadConfig)();
|
|
27
35
|
pool = new pg_1.Pool({ connectionString: config.databaseUrl });
|
|
28
|
-
db = (0, node_postgres_1.drizzle)(pool, { schema });
|
|
36
|
+
db = (0, node_postgres_1.drizzle)(pool, { schema: getSchema() });
|
|
29
37
|
return db;
|
|
30
38
|
}
|
|
31
39
|
function getTenantDb(schemaName) {
|
|
32
40
|
const config = (0, config_1.loadConfig)();
|
|
33
41
|
const url = `${config.databaseUrl}${config.databaseUrl.includes('?') ? '&' : '?'}options=-csearch_path%3D${schemaName}%2Cpublic`;
|
|
34
42
|
const tenantPool = new pg_1.Pool({ connectionString: url });
|
|
35
|
-
return (0, node_postgres_1.drizzle)(tenantPool, { schema });
|
|
43
|
+
return (0, node_postgres_1.drizzle)(tenantPool, { schema: getSchema() });
|
|
36
44
|
}
|
|
37
45
|
async function getAllTenants() {
|
|
46
|
+
const s = getSchema();
|
|
38
47
|
const publicDb = getPublicDb();
|
|
39
|
-
return publicDb.select().from(
|
|
48
|
+
return publicDb.select().from(s.tenants);
|
|
40
49
|
}
|
|
41
50
|
async function getTenantByName(name) {
|
|
51
|
+
const s = getSchema();
|
|
42
52
|
const publicDb = getPublicDb();
|
|
43
53
|
const [tenant] = await publicDb
|
|
44
54
|
.select()
|
|
45
|
-
.from(
|
|
46
|
-
.where((0, drizzle_orm_1.eq)(
|
|
55
|
+
.from(s.tenants)
|
|
56
|
+
.where((0, drizzle_orm_1.eq)(s.tenants.name, name));
|
|
47
57
|
return tenant || null;
|
|
48
58
|
}
|
|
49
59
|
async function testConnection() {
|