@openfactu/cli 0.0.6 → 0.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/LICENSE +21 -0
- package/README.md +164 -8
- package/dist/src/commands/backup.d.ts +2 -0
- package/dist/src/commands/backup.js +424 -0
- package/dist/src/commands/deploy.js +492 -69
- package/dist/src/commands/doctor.d.ts +2 -0
- package/dist/src/commands/doctor.js +295 -0
- package/dist/src/commands/install-quick.d.ts +2 -0
- package/dist/src/commands/install-quick.js +249 -0
- package/dist/src/commands/install-script.d.ts +2 -0
- package/dist/src/commands/install-script.js +474 -0
- package/dist/src/commands/install.js +969 -75
- package/dist/src/commands/monitoring.d.ts +2 -0
- package/dist/src/commands/monitoring.js +352 -0
- package/dist/src/commands/service.d.ts +2 -0
- package/dist/src/commands/service.js +402 -0
- package/dist/src/commands/setup.js +7 -2
- package/dist/src/commands/sync-ports.d.ts +2 -0
- package/dist/src/commands/sync-ports.js +298 -0
- package/dist/src/commands/uninstall.d.ts +2 -0
- package/dist/src/commands/uninstall.js +189 -0
- package/dist/src/index.js +17 -1
- package/dist/src/utils/config.d.ts +8 -0
- package/dist/src/utils/config.js +25 -1
- package/dist/src/utils/env.d.ts +11 -0
- package/dist/src/utils/env.js +31 -0
- package/dist/src/utils/helpers.d.ts +22 -0
- package/dist/src/utils/helpers.js +244 -0
- package/dist/src/utils/monitoring.d.ts +38 -0
- package/dist/src/utils/monitoring.js +353 -0
- package/dist/src/utils/paths.d.ts +1 -0
- package/dist/src/utils/paths.js +2 -0
- package/package.json +8 -5
|
@@ -0,0 +1,298 @@
|
|
|
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.registerSyncPortsCommand = registerSyncPortsCommand;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const ora_1 = __importDefault(require("ora"));
|
|
9
|
+
const inquirer_1 = __importDefault(require("inquirer"));
|
|
10
|
+
const fs_1 = __importDefault(require("fs"));
|
|
11
|
+
const path_1 = __importDefault(require("path"));
|
|
12
|
+
const logger_1 = require("../utils/logger");
|
|
13
|
+
const paths_1 = require("../utils/paths");
|
|
14
|
+
function readEnv(envPath) {
|
|
15
|
+
const env = {};
|
|
16
|
+
if (!fs_1.default.existsSync(envPath))
|
|
17
|
+
return env;
|
|
18
|
+
const lines = fs_1.default.readFileSync(envPath, 'utf-8').split('\n');
|
|
19
|
+
for (const line of lines) {
|
|
20
|
+
const trimmed = line.trim();
|
|
21
|
+
if (!trimmed || trimmed.startsWith('#'))
|
|
22
|
+
continue;
|
|
23
|
+
const eqIdx = trimmed.indexOf('=');
|
|
24
|
+
if (eqIdx > 0) {
|
|
25
|
+
env[trimmed.substring(0, eqIdx).trim()] = trimmed.substring(eqIdx + 1).trim();
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return env;
|
|
29
|
+
}
|
|
30
|
+
function writeEnv(envPath, env) {
|
|
31
|
+
const lines = [];
|
|
32
|
+
for (const [key, value] of Object.entries(env)) {
|
|
33
|
+
lines.push(`${key}=${value}`);
|
|
34
|
+
}
|
|
35
|
+
fs_1.default.writeFileSync(envPath, lines.join('\n') + '\n');
|
|
36
|
+
}
|
|
37
|
+
function extractPortsFromCompose(composeContent) {
|
|
38
|
+
const ports = {};
|
|
39
|
+
const portRegex = /"([^"]*):(\d+)"|-\s*"(\d+):(\d+)"/g;
|
|
40
|
+
let match;
|
|
41
|
+
while ((match = portRegex.exec(composeContent)) !== null) {
|
|
42
|
+
const hostPort = match[2] || match[3];
|
|
43
|
+
const containerPort = match[4] || '';
|
|
44
|
+
if (hostPort) {
|
|
45
|
+
const portNum = parseInt(hostPort);
|
|
46
|
+
if (containerPort) {
|
|
47
|
+
const containerNum = parseInt(containerPort);
|
|
48
|
+
if (containerNum === 5432)
|
|
49
|
+
ports.DB_PORT = portNum;
|
|
50
|
+
if (containerNum === 3000)
|
|
51
|
+
ports.SERVER_PORT = portNum;
|
|
52
|
+
if (containerNum === 80)
|
|
53
|
+
ports.WEB_PORT = portNum;
|
|
54
|
+
if (containerNum === 443)
|
|
55
|
+
ports.HTTPS_PORT = portNum;
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
if (portNum === 5432 || portNum === 5433)
|
|
59
|
+
ports.DB_PORT = portNum;
|
|
60
|
+
if (portNum === 3000 || portNum === 3001)
|
|
61
|
+
ports.SERVER_PORT = portNum;
|
|
62
|
+
if (portNum === 8080 || portNum === 8081)
|
|
63
|
+
ports.WEB_PORT = portNum;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return ports;
|
|
68
|
+
}
|
|
69
|
+
function updateComposePorts(composePath, ports) {
|
|
70
|
+
if (!fs_1.default.existsSync(composePath))
|
|
71
|
+
return false;
|
|
72
|
+
let content = fs_1.default.readFileSync(composePath, 'utf-8');
|
|
73
|
+
let changed = false;
|
|
74
|
+
const portMappings = {
|
|
75
|
+
'5432': { container: 5432, envKey: 'DB_PORT' },
|
|
76
|
+
'3000': { container: 3000, envKey: 'SERVER_PORT' },
|
|
77
|
+
'80': { container: 80, envKey: 'WEB_PORT' },
|
|
78
|
+
};
|
|
79
|
+
for (const [containerPort, { envKey }] of Object.entries(portMappings)) {
|
|
80
|
+
const newPort = ports[envKey];
|
|
81
|
+
if (!newPort)
|
|
82
|
+
continue;
|
|
83
|
+
const containerNum = parseInt(containerPort);
|
|
84
|
+
// Pattern: "hostPort:containerPort"
|
|
85
|
+
const pattern1 = new RegExp(`"([^"]*):${containerNum}"`, 'g');
|
|
86
|
+
const match1 = content.match(pattern1);
|
|
87
|
+
if (match1) {
|
|
88
|
+
content = content.replace(pattern1, `"${newPort}:${containerNum}"`);
|
|
89
|
+
changed = true;
|
|
90
|
+
}
|
|
91
|
+
// Pattern: - "hostPort:containerPort"
|
|
92
|
+
const pattern2 = new RegExp(`-\\s*"([^"]*):${containerNum}"`, 'g');
|
|
93
|
+
const match2 = content.match(pattern2);
|
|
94
|
+
if (match2) {
|
|
95
|
+
content = content.replace(pattern2, `- "${newPort}:${containerNum}"`);
|
|
96
|
+
changed = true;
|
|
97
|
+
}
|
|
98
|
+
// Pattern: - "0.0.0.0:hostPort:containerPort"
|
|
99
|
+
const pattern3 = new RegExp(`-\\s*"0\\.0\\.0\\.0:([^:]*):${containerNum}"`, 'g');
|
|
100
|
+
const match3 = content.match(pattern3);
|
|
101
|
+
if (match3) {
|
|
102
|
+
content = content.replace(pattern3, `- "0.0.0.0:${newPort}:${containerNum}"`);
|
|
103
|
+
changed = true;
|
|
104
|
+
}
|
|
105
|
+
// Pattern: - "127.0.0.1:hostPort:containerPort"
|
|
106
|
+
const pattern4 = new RegExp(`-\\s*"127\\.0\\.0\\.1:([^:]*):${containerNum}"`, 'g');
|
|
107
|
+
const match4 = content.match(pattern4);
|
|
108
|
+
if (match4) {
|
|
109
|
+
content = content.replace(pattern4, `- "127.0.0.1:${newPort}:${containerNum}"`);
|
|
110
|
+
changed = true;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (changed) {
|
|
114
|
+
fs_1.default.writeFileSync(composePath, content);
|
|
115
|
+
}
|
|
116
|
+
return changed;
|
|
117
|
+
}
|
|
118
|
+
function registerSyncPortsCommand(program) {
|
|
119
|
+
const syncCmd = program
|
|
120
|
+
.command('sync:ports')
|
|
121
|
+
.description('Sincroniza puertos entre .env y docker-compose files');
|
|
122
|
+
syncCmd
|
|
123
|
+
.command('env-to-compose')
|
|
124
|
+
.description('Copia puertos del .env a los docker-compose files')
|
|
125
|
+
.option('--path <path>', 'Ruta del proyecto')
|
|
126
|
+
.action(async (opts) => {
|
|
127
|
+
console.log();
|
|
128
|
+
console.log(chalk_1.default.bold.white(' OpenFactu — Sync Puertos (.env → compose)'));
|
|
129
|
+
console.log(chalk_1.default.dim(' ────────────────────────────────────'));
|
|
130
|
+
console.log();
|
|
131
|
+
try {
|
|
132
|
+
const root = opts.path || (0, paths_1.getProjectRoot)();
|
|
133
|
+
const envPath = path_1.default.join(root, '.env');
|
|
134
|
+
const env = readEnv(envPath);
|
|
135
|
+
const ports = {};
|
|
136
|
+
if (env.DB_PORT)
|
|
137
|
+
ports.DB_PORT = parseInt(env.DB_PORT);
|
|
138
|
+
if (env.SERVER_PORT)
|
|
139
|
+
ports.SERVER_PORT = parseInt(env.SERVER_PORT);
|
|
140
|
+
if (env.WEB_PORT)
|
|
141
|
+
ports.WEB_PORT = parseInt(env.WEB_PORT);
|
|
142
|
+
if (Object.keys(ports).length === 0) {
|
|
143
|
+
logger_1.log.warn('No se encontraron puertos en .env');
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
logger_1.log.info('Puertos en .env:');
|
|
147
|
+
for (const [key, value] of Object.entries(ports)) {
|
|
148
|
+
logger_1.log.info(` ${key}: ${value}`);
|
|
149
|
+
}
|
|
150
|
+
logger_1.log.blank();
|
|
151
|
+
const composeFiles = [
|
|
152
|
+
'docker-compose.yml',
|
|
153
|
+
'docker-compose.prod.yml',
|
|
154
|
+
];
|
|
155
|
+
for (const composeFile of composeFiles) {
|
|
156
|
+
const composePath = path_1.default.join(root, composeFile);
|
|
157
|
+
if (fs_1.default.existsSync(composePath)) {
|
|
158
|
+
const spinner = (0, ora_1.default)(`Actualizando ${composeFile}...`).start();
|
|
159
|
+
const changed = updateComposePorts(composePath, ports);
|
|
160
|
+
if (changed) {
|
|
161
|
+
spinner.succeed(`${composeFile} actualizado`);
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
spinner.info(`${composeFile} no necesita cambios`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
logger_1.log.blank();
|
|
169
|
+
logger_1.log.success('Sincronización completada');
|
|
170
|
+
logger_1.log.dim(' Reinicia los servicios con: docker compose up -d');
|
|
171
|
+
}
|
|
172
|
+
catch (err) {
|
|
173
|
+
logger_1.log.error(err.message);
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
syncCmd
|
|
177
|
+
.command('compose-to-env')
|
|
178
|
+
.description('Copia puertos de docker-compose al .env')
|
|
179
|
+
.option('--path <path>', 'Ruta del proyecto')
|
|
180
|
+
.action(async (opts) => {
|
|
181
|
+
console.log();
|
|
182
|
+
console.log(chalk_1.default.bold.white(' OpenFactu — Sync Puertos (compose → .env)'));
|
|
183
|
+
console.log(chalk_1.default.dim(' ────────────────────────────────────'));
|
|
184
|
+
console.log();
|
|
185
|
+
try {
|
|
186
|
+
const root = opts.path || (0, paths_1.getProjectRoot)();
|
|
187
|
+
const envPath = path_1.default.join(root, '.env');
|
|
188
|
+
const env = readEnv(envPath);
|
|
189
|
+
const composeFiles = [
|
|
190
|
+
'docker-compose.prod.yml',
|
|
191
|
+
'docker-compose.yml',
|
|
192
|
+
];
|
|
193
|
+
let foundPorts = {};
|
|
194
|
+
for (const composeFile of composeFiles) {
|
|
195
|
+
const composePath = path_1.default.join(root, composeFile);
|
|
196
|
+
if (fs_1.default.existsSync(composePath)) {
|
|
197
|
+
const content = fs_1.default.readFileSync(composePath, 'utf-8');
|
|
198
|
+
const ports = extractPortsFromCompose(content);
|
|
199
|
+
foundPorts = { ...foundPorts, ...ports };
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if (Object.keys(foundPorts).length === 0) {
|
|
203
|
+
logger_1.log.warn('No se encontraron puertos en docker-compose files');
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
logger_1.log.info('Puertos detectados en compose:');
|
|
207
|
+
for (const [key, value] of Object.entries(foundPorts)) {
|
|
208
|
+
const oldVal = env[key];
|
|
209
|
+
const marker = oldVal && parseInt(oldVal) !== value ? chalk_1.default.yellow(' (cambiado)') : '';
|
|
210
|
+
logger_1.log.info(` ${key}: ${value}${marker}`);
|
|
211
|
+
}
|
|
212
|
+
logger_1.log.blank();
|
|
213
|
+
const { confirm } = await inquirer_1.default.prompt([
|
|
214
|
+
{
|
|
215
|
+
type: 'confirm',
|
|
216
|
+
name: 'confirm',
|
|
217
|
+
message: 'Actualizar .env con estos puertos?',
|
|
218
|
+
default: true,
|
|
219
|
+
},
|
|
220
|
+
]);
|
|
221
|
+
if (!confirm) {
|
|
222
|
+
logger_1.log.info('Cancelado');
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
for (const [key, value] of Object.entries(foundPorts)) {
|
|
226
|
+
env[key] = String(value);
|
|
227
|
+
}
|
|
228
|
+
writeEnv(envPath, env);
|
|
229
|
+
logger_1.log.success('.env actualizado');
|
|
230
|
+
logger_1.log.dim(' Reinicia los servicios con: docker compose up -d');
|
|
231
|
+
}
|
|
232
|
+
catch (err) {
|
|
233
|
+
logger_1.log.error(err.message);
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
syncCmd
|
|
237
|
+
.command('check')
|
|
238
|
+
.description('Verifica si hay diferencias de puertos entre .env y compose')
|
|
239
|
+
.option('--path <path>', 'Ruta del proyecto')
|
|
240
|
+
.action(async (opts) => {
|
|
241
|
+
try {
|
|
242
|
+
const root = opts.path || (0, paths_1.getProjectRoot)();
|
|
243
|
+
const envPath = path_1.default.join(root, '.env');
|
|
244
|
+
const env = readEnv(envPath);
|
|
245
|
+
const composeFiles = [
|
|
246
|
+
'docker-compose.prod.yml',
|
|
247
|
+
'docker-compose.yml',
|
|
248
|
+
];
|
|
249
|
+
let composePorts = {};
|
|
250
|
+
for (const composeFile of composeFiles) {
|
|
251
|
+
const composePath = path_1.default.join(root, composeFile);
|
|
252
|
+
if (fs_1.default.existsSync(composePath)) {
|
|
253
|
+
const content = fs_1.default.readFileSync(composePath, 'utf-8');
|
|
254
|
+
composePorts = { ...composePorts, ...extractPortsFromCompose(content) };
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
const envPorts = {};
|
|
258
|
+
if (env.DB_PORT)
|
|
259
|
+
envPorts.DB_PORT = parseInt(env.DB_PORT);
|
|
260
|
+
if (env.SERVER_PORT)
|
|
261
|
+
envPorts.SERVER_PORT = parseInt(env.SERVER_PORT);
|
|
262
|
+
if (env.WEB_PORT)
|
|
263
|
+
envPorts.WEB_PORT = parseInt(env.WEB_PORT);
|
|
264
|
+
logger_1.log.blank();
|
|
265
|
+
logger_1.log.info('Comparación de puertos:');
|
|
266
|
+
logger_1.log.blank();
|
|
267
|
+
const allKeys = [...new Set([...Object.keys(envPorts), ...Object.keys(composePorts)])];
|
|
268
|
+
let hasDifferences = false;
|
|
269
|
+
for (const key of allKeys.sort()) {
|
|
270
|
+
const envVal = envPorts[key];
|
|
271
|
+
const composeVal = composePorts[key];
|
|
272
|
+
if (envVal && composeVal && envVal !== composeVal) {
|
|
273
|
+
logger_1.log.warn(` ${key}: .env=${envVal} | compose=${composeVal} ${chalk_1.default.red('← DIFERENTE')}`);
|
|
274
|
+
hasDifferences = true;
|
|
275
|
+
}
|
|
276
|
+
else if (envVal) {
|
|
277
|
+
logger_1.log.info(` ${key}: ${envVal} ${chalk_1.default.dim('(solo en .env)')}`);
|
|
278
|
+
}
|
|
279
|
+
else if (composeVal) {
|
|
280
|
+
logger_1.log.info(` ${key}: ${composeVal} ${chalk_1.default.dim('(solo en compose)')}`);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
logger_1.log.blank();
|
|
284
|
+
if (!hasDifferences) {
|
|
285
|
+
logger_1.log.success('Todos los puertos están sincronizados');
|
|
286
|
+
}
|
|
287
|
+
else {
|
|
288
|
+
logger_1.log.warn('Hay diferencias. Usa uno de estos comandos para sincronizar:');
|
|
289
|
+
logger_1.log.dim(' openfactu sync:ports env-to-compose');
|
|
290
|
+
logger_1.log.dim(' openfactu sync:ports compose-to-env');
|
|
291
|
+
}
|
|
292
|
+
logger_1.log.blank();
|
|
293
|
+
}
|
|
294
|
+
catch (err) {
|
|
295
|
+
logger_1.log.error(err.message);
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
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.registerUninstallCommand = registerUninstallCommand;
|
|
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
|
+
const helpers_1 = require("../utils/helpers");
|
|
17
|
+
function registerUninstallCommand(program) {
|
|
18
|
+
program
|
|
19
|
+
.command('uninstall')
|
|
20
|
+
.description('Desinstalar OpenFactu de forma limpia')
|
|
21
|
+
.option('--path <path>', 'Ruta del proyecto a desinstalar')
|
|
22
|
+
.option('--force', 'No preguntar confirmacion')
|
|
23
|
+
.option('--keep-data', 'Mantener datos (BD, storage, configs)')
|
|
24
|
+
.option('--keep-backup', 'Crear backup antes de desinstalar')
|
|
25
|
+
.option('--remove-global', 'Remover CLI global tambien')
|
|
26
|
+
.action(async (opts) => {
|
|
27
|
+
console.log();
|
|
28
|
+
console.log(chalk_1.default.bold.white(' OpenFactu — Desinstalacion'));
|
|
29
|
+
console.log(chalk_1.default.dim(' ────────────────────────────────────'));
|
|
30
|
+
console.log();
|
|
31
|
+
try {
|
|
32
|
+
const root = opts.path || (0, paths_1.getProjectRoot)();
|
|
33
|
+
if (!fs_1.default.existsSync(root)) {
|
|
34
|
+
logger_1.log.error(`Directorio no encontrado: ${root}`);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
// Verificar que es un proyecto OpenFactu
|
|
38
|
+
const pkgPath = path_1.default.join(root, 'package.json');
|
|
39
|
+
if (!fs_1.default.existsSync(pkgPath)) {
|
|
40
|
+
logger_1.log.error('No parece ser un proyecto OpenFactu valido');
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (!opts.force) {
|
|
44
|
+
logger_1.log.warn(`${chalk_1.default.red('ADVERTENCIA:')} Esta accion es destructiva`);
|
|
45
|
+
logger_1.log.blank();
|
|
46
|
+
logger_1.log.info(`Proyecto: ${chalk_1.default.cyan(root)}`);
|
|
47
|
+
const diskUsage = (0, child_process_1.execSync)(`du -sh "${root}" 2>/dev/null | cut -f1`, { stdio: 'pipe' }).toString().trim();
|
|
48
|
+
logger_1.log.info(`Tamano: ${chalk_1.default.cyan(diskUsage)}`);
|
|
49
|
+
logger_1.log.blank();
|
|
50
|
+
const { confirm } = await inquirer_1.default.prompt([
|
|
51
|
+
{
|
|
52
|
+
type: 'confirm',
|
|
53
|
+
name: 'confirm',
|
|
54
|
+
message: chalk_1.default.red('Estas seguro? Esta accion no se puede deshacer'),
|
|
55
|
+
default: false,
|
|
56
|
+
},
|
|
57
|
+
]);
|
|
58
|
+
if (!confirm) {
|
|
59
|
+
logger_1.log.info('Desinstalacion cancelada');
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// Backup opcional
|
|
64
|
+
if (opts.keepBackup) {
|
|
65
|
+
const backupDir = path_1.default.join(os_1.default.homedir(), 'openfactu-backups', `backup-${(0, helpers_1.timestamp)()}`);
|
|
66
|
+
const backupSpinner = (0, ora_1.default)('Creando backup...').start();
|
|
67
|
+
try {
|
|
68
|
+
(0, helpers_1.ensureDir)(backupDir);
|
|
69
|
+
const dirsToBackup = ['storage', '.env', 'docker-compose*.yml', 'monitoring'];
|
|
70
|
+
for (const item of dirsToBackup) {
|
|
71
|
+
const src = path_1.default.join(root, item);
|
|
72
|
+
if (fs_1.default.existsSync(src)) {
|
|
73
|
+
const dest = path_1.default.join(backupDir, path_1.default.basename(item));
|
|
74
|
+
if (fs_1.default.statSync(src).isDirectory()) {
|
|
75
|
+
(0, helpers_1.copyDirRecursive)(src, dest);
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
fs_1.default.copyFileSync(src, dest);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// Database backup
|
|
83
|
+
const dockerCmd = (0, helpers_1.getDockerComposeCommand)();
|
|
84
|
+
const dbBackupPath = path_1.default.join(backupDir, 'database.sql');
|
|
85
|
+
(0, child_process_1.execSync)(`${dockerCmd} exec -T db pg_dump -U openfactu openfactudb > "${dbBackupPath}" 2>/dev/null || true`, {
|
|
86
|
+
stdio: 'pipe',
|
|
87
|
+
});
|
|
88
|
+
backupSpinner.succeed(`Backup creado: ${chalk_1.default.cyan(backupDir)}`);
|
|
89
|
+
}
|
|
90
|
+
catch (err) {
|
|
91
|
+
backupSpinner.warn('Backup parcial (algunos archivos no se pudieron copiar)');
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// Parar servicios Docker
|
|
95
|
+
const dockerSpinner = (0, ora_1.default)('Parando servicios Docker...').start();
|
|
96
|
+
try {
|
|
97
|
+
const dockerCmd = (0, helpers_1.getDockerComposeCommand)();
|
|
98
|
+
const composeFiles = [];
|
|
99
|
+
const possibleFiles = [
|
|
100
|
+
'docker-compose.prod.yml',
|
|
101
|
+
'docker-compose.prod.monitoring.yml',
|
|
102
|
+
'docker-compose.monitoring.yml',
|
|
103
|
+
'docker-compose.yml',
|
|
104
|
+
];
|
|
105
|
+
for (const f of possibleFiles) {
|
|
106
|
+
if (fs_1.default.existsSync(path_1.default.join(root, f))) {
|
|
107
|
+
composeFiles.push(f);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (composeFiles.length > 0) {
|
|
111
|
+
const fileFlags = composeFiles.map(f => `-f ${f}`).join(' ');
|
|
112
|
+
(0, child_process_1.execSync)(`${dockerCmd} ${fileFlags} down -v`, { cwd: root, stdio: 'pipe' });
|
|
113
|
+
}
|
|
114
|
+
// Remover imagenes del proyecto
|
|
115
|
+
try {
|
|
116
|
+
(0, child_process_1.execSync)(`${dockerCmd} -f docker-compose.yml rm -f 2>/dev/null || true`, { cwd: root, stdio: 'pipe' });
|
|
117
|
+
}
|
|
118
|
+
catch { }
|
|
119
|
+
dockerSpinner.succeed('Servicios parados');
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
dockerSpinner.warn('No se pudieron parar todos los servicios');
|
|
123
|
+
}
|
|
124
|
+
// Remover servicio systemd si existe
|
|
125
|
+
const serviceSpinner = (0, ora_1.default)('Verificando servicios systemd...').start();
|
|
126
|
+
try {
|
|
127
|
+
const services = ['openfactu', 'openfactu-platform'];
|
|
128
|
+
for (const svc of services) {
|
|
129
|
+
const unitPath = `/etc/systemd/system/${svc}.service`;
|
|
130
|
+
if (fs_1.default.existsSync(unitPath)) {
|
|
131
|
+
(0, child_process_1.execSync)(`sudo systemctl stop ${svc} 2>/dev/null || true`, { stdio: 'pipe' });
|
|
132
|
+
(0, child_process_1.execSync)(`sudo systemctl disable ${svc} 2>/dev/null || true`, { stdio: 'pipe' });
|
|
133
|
+
(0, child_process_1.execSync)(`sudo rm ${unitPath}`, { stdio: 'pipe' });
|
|
134
|
+
logger_1.log.info(`Servicio ${svc} removido`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
(0, child_process_1.execSync)('sudo systemctl daemon-reload 2>/dev/null || true', { stdio: 'pipe' });
|
|
138
|
+
serviceSpinner.succeed('Servicios systemd verificados');
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
serviceSpinner.warn('No se encontraron servicios systemd');
|
|
142
|
+
}
|
|
143
|
+
// Remover archivos
|
|
144
|
+
if (!opts.keepData) {
|
|
145
|
+
const removeSpinner = (0, ora_1.default)('Removiendo archivos del proyecto...').start();
|
|
146
|
+
try {
|
|
147
|
+
// Remover directorios grandes primero
|
|
148
|
+
const largeDirs = ['node_modules', 'storage', '.git'];
|
|
149
|
+
for (const dir of largeDirs) {
|
|
150
|
+
const dirPath = path_1.default.join(root, dir);
|
|
151
|
+
if (fs_1.default.existsSync(dirPath)) {
|
|
152
|
+
(0, child_process_1.execSync)(`rm -rf "${dirPath}"`, { stdio: 'pipe' });
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
// Remover resto
|
|
156
|
+
(0, child_process_1.execSync)(`rm -rf "${root}"`, { stdio: 'pipe' });
|
|
157
|
+
removeSpinner.succeed('Archivos removidos');
|
|
158
|
+
}
|
|
159
|
+
catch (err) {
|
|
160
|
+
removeSpinner.fail('Error al remover archivos');
|
|
161
|
+
logger_1.log.dim(` Remueve manualmente: rm -rf ${root}`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
logger_1.log.info('Datos mantenidos en: ' + chalk_1.default.cyan(root));
|
|
166
|
+
}
|
|
167
|
+
// Remover CLI global
|
|
168
|
+
if (opts.removeGlobal) {
|
|
169
|
+
const cliSpinner = (0, ora_1.default)('Removiendo CLI global...').start();
|
|
170
|
+
try {
|
|
171
|
+
(0, child_process_1.execSync)('npm uninstall -g @openfactu/cli', { stdio: 'pipe' });
|
|
172
|
+
cliSpinner.succeed('CLI global removido');
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
cliSpinner.warn('No se pudo remover el CLI global');
|
|
176
|
+
logger_1.log.dim(' Ejecuta: npm uninstall -g @openfactu/cli');
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
logger_1.log.blank();
|
|
180
|
+
console.log(chalk_1.default.bold.green(' Desinstalacion completada'));
|
|
181
|
+
console.log(chalk_1.default.dim(' ────────────────────────────────────'));
|
|
182
|
+
logger_1.log.blank();
|
|
183
|
+
}
|
|
184
|
+
catch (err) {
|
|
185
|
+
logger_1.log.error(err.message);
|
|
186
|
+
process.exitCode = 1;
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
}
|
package/dist/src/index.js
CHANGED
|
@@ -10,12 +10,20 @@ const setup_1 = require("./commands/setup");
|
|
|
10
10
|
const update_1 = require("./commands/update");
|
|
11
11
|
const install_1 = require("./commands/install");
|
|
12
12
|
const deploy_1 = require("./commands/deploy");
|
|
13
|
+
const monitoring_1 = require("./commands/monitoring");
|
|
14
|
+
const service_1 = require("./commands/service");
|
|
15
|
+
const uninstall_1 = require("./commands/uninstall");
|
|
16
|
+
const backup_1 = require("./commands/backup");
|
|
17
|
+
const doctor_1 = require("./commands/doctor");
|
|
18
|
+
const install_quick_1 = require("./commands/install-quick");
|
|
19
|
+
const install_script_1 = require("./commands/install-script");
|
|
20
|
+
const sync_ports_1 = require("./commands/sync-ports");
|
|
13
21
|
function createCLI() {
|
|
14
22
|
const program = new commander_1.Command();
|
|
15
23
|
program
|
|
16
24
|
.name('openfactu')
|
|
17
25
|
.description('CLI para gestionar OpenFactu')
|
|
18
|
-
.version('0.0.
|
|
26
|
+
.version('0.0.7');
|
|
19
27
|
(0, version_1.registerVersionCommand)(program);
|
|
20
28
|
(0, migrate_1.registerMigrateCommand)(program);
|
|
21
29
|
(0, tenant_1.registerTenantCommand)(program);
|
|
@@ -23,6 +31,14 @@ function createCLI() {
|
|
|
23
31
|
(0, setup_1.registerSetupCommand)(program);
|
|
24
32
|
(0, update_1.registerUpdateCommand)(program);
|
|
25
33
|
(0, install_1.registerInstallCommand)(program);
|
|
34
|
+
(0, install_quick_1.registerInstallQuickCommand)(program);
|
|
35
|
+
(0, install_script_1.registerInstallScriptCommand)(program);
|
|
36
|
+
(0, sync_ports_1.registerSyncPortsCommand)(program);
|
|
26
37
|
(0, deploy_1.registerDeployCommand)(program);
|
|
38
|
+
(0, monitoring_1.registerMonitoringCommand)(program);
|
|
39
|
+
(0, service_1.registerServiceCommand)(program);
|
|
40
|
+
(0, uninstall_1.registerUninstallCommand)(program);
|
|
41
|
+
(0, backup_1.registerBackupCommand)(program);
|
|
42
|
+
(0, doctor_1.registerDoctorCommand)(program);
|
|
27
43
|
return program;
|
|
28
44
|
}
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* El CLI se ejecuta en el HOST, pero el DATABASE_URL del .env apunta al
|
|
3
|
+
* hostname interno de Docker ('db'), que solo resuelve dentro de la red de
|
|
4
|
+
* contenedores. Esta función lo reescribe a 127.0.0.1 con el puerto publicado
|
|
5
|
+
* (DB_PORT) para que los comandos de host (setup, migrate, tenant) puedan
|
|
6
|
+
* conectar a la base de datos a través del puerto mapeado.
|
|
7
|
+
*/
|
|
8
|
+
export declare function resolveHostDatabaseUrl(databaseUrl: string, dbPort?: string): string;
|
|
1
9
|
/**
|
|
2
10
|
* Carga la configuración desde .env del proyecto OpenFactu.
|
|
3
11
|
*/
|
package/dist/src/utils/config.js
CHANGED
|
@@ -3,10 +3,33 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.resolveHostDatabaseUrl = resolveHostDatabaseUrl;
|
|
6
7
|
exports.loadConfig = loadConfig;
|
|
7
8
|
const dotenv_1 = __importDefault(require("dotenv"));
|
|
8
9
|
const fs_1 = __importDefault(require("fs"));
|
|
9
10
|
const paths_1 = require("./paths");
|
|
11
|
+
/**
|
|
12
|
+
* El CLI se ejecuta en el HOST, pero el DATABASE_URL del .env apunta al
|
|
13
|
+
* hostname interno de Docker ('db'), que solo resuelve dentro de la red de
|
|
14
|
+
* contenedores. Esta función lo reescribe a 127.0.0.1 con el puerto publicado
|
|
15
|
+
* (DB_PORT) para que los comandos de host (setup, migrate, tenant) puedan
|
|
16
|
+
* conectar a la base de datos a través del puerto mapeado.
|
|
17
|
+
*/
|
|
18
|
+
function resolveHostDatabaseUrl(databaseUrl, dbPort) {
|
|
19
|
+
try {
|
|
20
|
+
const u = new URL(databaseUrl);
|
|
21
|
+
if (u.hostname === 'db') {
|
|
22
|
+
u.hostname = '127.0.0.1';
|
|
23
|
+
if (dbPort)
|
|
24
|
+
u.port = dbPort;
|
|
25
|
+
return u.toString();
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
// URL no parseable: la devolvemos tal cual y dejamos que falle más arriba.
|
|
30
|
+
}
|
|
31
|
+
return databaseUrl;
|
|
32
|
+
}
|
|
10
33
|
/**
|
|
11
34
|
* Carga la configuración desde .env del proyecto OpenFactu.
|
|
12
35
|
*/
|
|
@@ -15,8 +38,9 @@ function loadConfig() {
|
|
|
15
38
|
if (fs_1.default.existsSync(envPath)) {
|
|
16
39
|
dotenv_1.default.config({ path: envPath });
|
|
17
40
|
}
|
|
41
|
+
const rawDatabaseUrl = process.env.DATABASE_URL || 'postgresql://openfactu:openfactu_pass@localhost:5432/openfactudb';
|
|
18
42
|
return {
|
|
19
|
-
databaseUrl: process.env.
|
|
43
|
+
databaseUrl: resolveHostDatabaseUrl(rawDatabaseUrl, process.env.DB_PORT),
|
|
20
44
|
jwtSecret: process.env.JWT_SECRET || 'super-secret-key',
|
|
21
45
|
serverPort: process.env.SERVER_PORT || '3000',
|
|
22
46
|
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Aplica un conjunto de overrides (clave=valor) sobre el contenido de un .env,
|
|
3
|
+
* preservando comentarios y orden del archivo base.
|
|
4
|
+
*
|
|
5
|
+
* - Si la clave ya existe, reemplaza solo su valor.
|
|
6
|
+
* - Si no existe, la añade al final.
|
|
7
|
+
*
|
|
8
|
+
* A diferencia de un merge ingenuo, opera sobre una única cadena acumulada para
|
|
9
|
+
* no duplicar el contenido del archivo en cada iteración.
|
|
10
|
+
*/
|
|
11
|
+
export declare function applyEnvOverrides(baseContent: string, overrides: Record<string, string>): string;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.applyEnvOverrides = applyEnvOverrides;
|
|
4
|
+
/**
|
|
5
|
+
* Aplica un conjunto de overrides (clave=valor) sobre el contenido de un .env,
|
|
6
|
+
* preservando comentarios y orden del archivo base.
|
|
7
|
+
*
|
|
8
|
+
* - Si la clave ya existe, reemplaza solo su valor.
|
|
9
|
+
* - Si no existe, la añade al final.
|
|
10
|
+
*
|
|
11
|
+
* A diferencia de un merge ingenuo, opera sobre una única cadena acumulada para
|
|
12
|
+
* no duplicar el contenido del archivo en cada iteración.
|
|
13
|
+
*/
|
|
14
|
+
function applyEnvOverrides(baseContent, overrides) {
|
|
15
|
+
let content = baseContent;
|
|
16
|
+
for (const [key, value] of Object.entries(overrides)) {
|
|
17
|
+
const re = new RegExp(`^${escapeRegExp(key)}=.*$`, 'm');
|
|
18
|
+
// Usamos la forma de función en replace para que '$' en el valor no se
|
|
19
|
+
// interprete como referencia de captura ($1, $&, $$...).
|
|
20
|
+
if (re.test(content)) {
|
|
21
|
+
content = content.replace(re, () => `${key}=${value}`);
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
content = content.trimEnd() + `\n${key}=${value}`;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return content.endsWith('\n') ? content : content + '\n';
|
|
28
|
+
}
|
|
29
|
+
function escapeRegExp(str) {
|
|
30
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
31
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface SystemCheck {
|
|
2
|
+
name: string;
|
|
3
|
+
status: 'pass' | 'warn' | 'fail';
|
|
4
|
+
message: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function generatePassword(length?: number): string;
|
|
7
|
+
export declare function generateSlug(length?: number): string;
|
|
8
|
+
export declare function checkDiskSpace(dir: string): {
|
|
9
|
+
availableGB: number;
|
|
10
|
+
totalGB: number;
|
|
11
|
+
};
|
|
12
|
+
export declare function checkPortInUse(port: number): boolean;
|
|
13
|
+
export declare function getRunningServices(): string[];
|
|
14
|
+
export declare function runPreflightChecks(targetDir?: string): SystemCheck[];
|
|
15
|
+
export declare function waitForService(url: string, maxAttempts?: number, intervalMs?: number): Promise<boolean>;
|
|
16
|
+
export declare function getDockerComposeCommand(): string;
|
|
17
|
+
export declare function isLinux(): boolean;
|
|
18
|
+
export declare function isSystemdAvailable(): boolean;
|
|
19
|
+
export declare function formatBytes(bytes: number): string;
|
|
20
|
+
export declare function timestamp(): string;
|
|
21
|
+
export declare function ensureDir(dir: string): void;
|
|
22
|
+
export declare function copyDirRecursive(src: string, dest: string): void;
|