@openfactu/cli 0.0.7 → 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/README.md +161 -5
- package/dist/src/commands/backup.d.ts +2 -0
- package/dist/src/commands/backup.js +424 -0
- package/dist/src/commands/deploy.js +486 -67
- 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 +966 -72
- 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,295 @@
|
|
|
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.registerDoctorCommand = registerDoctorCommand;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const child_process_1 = require("child_process");
|
|
9
|
+
const fs_1 = __importDefault(require("fs"));
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
11
|
+
const os_1 = __importDefault(require("os"));
|
|
12
|
+
const paths_1 = require("../utils/paths");
|
|
13
|
+
const helpers_1 = require("../utils/helpers");
|
|
14
|
+
function registerDoctorCommand(program) {
|
|
15
|
+
program
|
|
16
|
+
.command('doctor')
|
|
17
|
+
.description('Diagnostico completo del entorno OpenFactu')
|
|
18
|
+
.option('--path <path>', 'Ruta del proyecto')
|
|
19
|
+
.option('--json', 'Salida en formato JSON')
|
|
20
|
+
.action(async (opts) => {
|
|
21
|
+
console.log();
|
|
22
|
+
console.log(chalk_1.default.bold.white(' OpenFactu — Doctor'));
|
|
23
|
+
console.log(chalk_1.default.dim(' ────────────────────────────────────'));
|
|
24
|
+
console.log();
|
|
25
|
+
const checks = [];
|
|
26
|
+
// System info
|
|
27
|
+
checks.push({
|
|
28
|
+
category: 'Sistema',
|
|
29
|
+
name: 'Plataforma',
|
|
30
|
+
status: 'ok',
|
|
31
|
+
message: `${os_1.default.type()} ${os_1.default.release()} (${os_1.default.arch()})`,
|
|
32
|
+
});
|
|
33
|
+
checks.push({
|
|
34
|
+
category: 'Sistema',
|
|
35
|
+
name: 'Node.js',
|
|
36
|
+
status: 'ok',
|
|
37
|
+
message: process.version,
|
|
38
|
+
});
|
|
39
|
+
checks.push({
|
|
40
|
+
category: 'Sistema',
|
|
41
|
+
name: 'Memoria total',
|
|
42
|
+
status: 'ok',
|
|
43
|
+
message: (0, helpers_1.formatBytes)(os_1.default.totalmem()),
|
|
44
|
+
});
|
|
45
|
+
checks.push({
|
|
46
|
+
category: 'Sistema',
|
|
47
|
+
name: 'Memoria libre',
|
|
48
|
+
status: 'ok',
|
|
49
|
+
message: (0, helpers_1.formatBytes)(os_1.default.freemem()),
|
|
50
|
+
});
|
|
51
|
+
// Preflight checks
|
|
52
|
+
const root = opts.path || process.cwd();
|
|
53
|
+
const preflight = (0, helpers_1.runPreflightChecks)(root);
|
|
54
|
+
for (const check of preflight) {
|
|
55
|
+
checks.push({
|
|
56
|
+
category: 'Requisitos',
|
|
57
|
+
name: check.name,
|
|
58
|
+
status: check.status === 'pass' ? 'ok' : check.status === 'warn' ? 'warn' : 'error',
|
|
59
|
+
message: check.message,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
// Project structure
|
|
63
|
+
try {
|
|
64
|
+
const projectRoot = (0, paths_1.getProjectRoot)();
|
|
65
|
+
checks.push({
|
|
66
|
+
category: 'Proyecto',
|
|
67
|
+
name: 'Raiz detectada',
|
|
68
|
+
status: 'ok',
|
|
69
|
+
message: projectRoot,
|
|
70
|
+
});
|
|
71
|
+
// Check key files
|
|
72
|
+
const keyFiles = [
|
|
73
|
+
{ path: 'package.json', name: 'package.json' },
|
|
74
|
+
{ path: 'docker-compose.yml', name: 'docker-compose.yml' },
|
|
75
|
+
{ path: '.env', name: '.env' },
|
|
76
|
+
{ path: 'apps/web', name: 'apps/web' },
|
|
77
|
+
{ path: 'apps/server', name: 'apps/server' },
|
|
78
|
+
];
|
|
79
|
+
for (const file of keyFiles) {
|
|
80
|
+
const fullPath = path_1.default.join(projectRoot, file.path);
|
|
81
|
+
const exists = fs_1.default.existsSync(fullPath);
|
|
82
|
+
checks.push({
|
|
83
|
+
category: 'Proyecto',
|
|
84
|
+
name: file.name,
|
|
85
|
+
status: exists ? 'ok' : 'warn',
|
|
86
|
+
message: exists ? 'Presente' : 'No encontrado',
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
// Disk usage
|
|
90
|
+
const diskUsage = (0, child_process_1.execSync)(`du -sh "${projectRoot}" 2>/dev/null | cut -f1`, { stdio: 'pipe' }).toString().trim();
|
|
91
|
+
checks.push({
|
|
92
|
+
category: 'Proyecto',
|
|
93
|
+
name: 'Tamano del proyecto',
|
|
94
|
+
status: 'ok',
|
|
95
|
+
message: diskUsage,
|
|
96
|
+
});
|
|
97
|
+
// Disk space available
|
|
98
|
+
const disk = (0, helpers_1.checkDiskSpace)(projectRoot);
|
|
99
|
+
checks.push({
|
|
100
|
+
category: 'Proyecto',
|
|
101
|
+
name: 'Espacio disponible',
|
|
102
|
+
status: disk.availableGB >= 10 ? 'ok' : disk.availableGB >= 5 ? 'warn' : 'error',
|
|
103
|
+
message: `${disk.availableGB}GB libres de ${disk.totalGB}GB`,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
catch (err) {
|
|
107
|
+
checks.push({
|
|
108
|
+
category: 'Proyecto',
|
|
109
|
+
name: 'Raiz del proyecto',
|
|
110
|
+
status: 'error',
|
|
111
|
+
message: 'No detectado',
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
// Docker status
|
|
115
|
+
try {
|
|
116
|
+
const dockerCmd = (0, helpers_1.getDockerComposeCommand)();
|
|
117
|
+
const dockerInfo = (0, child_process_1.execSync)('docker info --format "{{.ServerVersion}}" 2>/dev/null || echo "unknown"', {
|
|
118
|
+
stdio: 'pipe',
|
|
119
|
+
}).toString().trim();
|
|
120
|
+
checks.push({
|
|
121
|
+
category: 'Docker',
|
|
122
|
+
name: 'Version del servidor',
|
|
123
|
+
status: 'ok',
|
|
124
|
+
message: dockerInfo,
|
|
125
|
+
});
|
|
126
|
+
// Running containers
|
|
127
|
+
const runningContainers = (0, child_process_1.execSync)('docker ps --format "{{.Names}}" 2>/dev/null || true', {
|
|
128
|
+
stdio: 'pipe',
|
|
129
|
+
}).toString().trim();
|
|
130
|
+
const containerList = runningContainers ? runningContainers.split('\n') : [];
|
|
131
|
+
checks.push({
|
|
132
|
+
category: 'Docker',
|
|
133
|
+
name: 'Contenedores corriendo',
|
|
134
|
+
status: containerList.length > 0 ? 'ok' : 'warn',
|
|
135
|
+
message: containerList.length > 0 ? containerList.join(', ') : 'Ninguno',
|
|
136
|
+
});
|
|
137
|
+
// Docker images
|
|
138
|
+
const imageCount = (0, child_process_1.execSync)('docker images --format "{{.ID}}" 2>/dev/null | wc -l', {
|
|
139
|
+
stdio: 'pipe',
|
|
140
|
+
}).toString().trim();
|
|
141
|
+
const dockerDisk = (0, child_process_1.execSync)('docker system df --format "{{.Size}}" 2>/dev/null | head -1 || echo "unknown"', {
|
|
142
|
+
stdio: 'pipe',
|
|
143
|
+
}).toString().trim();
|
|
144
|
+
checks.push({
|
|
145
|
+
category: 'Docker',
|
|
146
|
+
name: 'Imagenes',
|
|
147
|
+
status: 'ok',
|
|
148
|
+
message: `${imageCount} imagenes, ${dockerDisk} en disco`,
|
|
149
|
+
});
|
|
150
|
+
// Docker networks
|
|
151
|
+
const networks = (0, child_process_1.execSync)('docker network ls --format "{{.Name}}" 2>/dev/null | grep openfactu || true', {
|
|
152
|
+
stdio: 'pipe',
|
|
153
|
+
}).toString().trim();
|
|
154
|
+
checks.push({
|
|
155
|
+
category: 'Docker',
|
|
156
|
+
name: 'Redes OpenFactu',
|
|
157
|
+
status: networks ? 'ok' : 'warn',
|
|
158
|
+
message: networks || 'Ninguna detectada',
|
|
159
|
+
});
|
|
160
|
+
// Docker volumes
|
|
161
|
+
const volumes = (0, child_process_1.execSync)('docker volume ls --format "{{.Name}}" 2>/dev/null | grep openfactu || true', {
|
|
162
|
+
stdio: 'pipe',
|
|
163
|
+
}).toString().trim();
|
|
164
|
+
checks.push({
|
|
165
|
+
category: 'Docker',
|
|
166
|
+
name: 'Volumenes OpenFactu',
|
|
167
|
+
status: volumes ? 'ok' : 'warn',
|
|
168
|
+
message: volumes || 'Ninguno detectado',
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
catch (err) {
|
|
172
|
+
checks.push({
|
|
173
|
+
category: 'Docker',
|
|
174
|
+
name: 'Estado',
|
|
175
|
+
status: 'error',
|
|
176
|
+
message: err.message,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
// Services status
|
|
180
|
+
try {
|
|
181
|
+
const projectRoot = (0, paths_1.getProjectRoot)();
|
|
182
|
+
const dockerCmd = (0, helpers_1.getDockerComposeCommand)();
|
|
183
|
+
const composeFiles = [
|
|
184
|
+
'docker-compose.prod.yml',
|
|
185
|
+
'docker-compose.yml',
|
|
186
|
+
];
|
|
187
|
+
for (const composeFile of composeFiles) {
|
|
188
|
+
const composePath = path_1.default.join(projectRoot, composeFile);
|
|
189
|
+
if (fs_1.default.existsSync(composePath)) {
|
|
190
|
+
try {
|
|
191
|
+
const psOutput = (0, child_process_1.execSync)(`${dockerCmd} -f ${composeFile} ps --format json 2>/dev/null || true`, {
|
|
192
|
+
cwd: projectRoot,
|
|
193
|
+
stdio: 'pipe',
|
|
194
|
+
}).toString().trim();
|
|
195
|
+
if (psOutput) {
|
|
196
|
+
const services = psOutput.split('\n').filter(Boolean);
|
|
197
|
+
checks.push({
|
|
198
|
+
category: 'Servicios',
|
|
199
|
+
name: composeFile,
|
|
200
|
+
status: 'ok',
|
|
201
|
+
message: `${services.length} servicios definidos`,
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
catch {
|
|
206
|
+
checks.push({
|
|
207
|
+
category: 'Servicios',
|
|
208
|
+
name: composeFile,
|
|
209
|
+
status: 'warn',
|
|
210
|
+
message: 'No se pudo obtener estado',
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
catch { }
|
|
217
|
+
// Systemd services
|
|
218
|
+
try {
|
|
219
|
+
const services = (0, child_process_1.execSync)('systemctl list-units --type=service --state=running 2>/dev/null | grep openfactu || true', {
|
|
220
|
+
stdio: 'pipe',
|
|
221
|
+
}).toString().trim();
|
|
222
|
+
if (services) {
|
|
223
|
+
checks.push({
|
|
224
|
+
category: 'Systemd',
|
|
225
|
+
name: 'Servicios activos',
|
|
226
|
+
status: 'ok',
|
|
227
|
+
message: services,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
catch { }
|
|
232
|
+
// Git status
|
|
233
|
+
try {
|
|
234
|
+
const projectRoot = (0, paths_1.getProjectRoot)();
|
|
235
|
+
if (fs_1.default.existsSync(path_1.default.join(projectRoot, '.git'))) {
|
|
236
|
+
const branch = (0, child_process_1.execSync)('git branch --show-current 2>/dev/null || echo "detached"', {
|
|
237
|
+
cwd: projectRoot,
|
|
238
|
+
stdio: 'pipe',
|
|
239
|
+
}).toString().trim();
|
|
240
|
+
const status = (0, child_process_1.execSync)('git status --porcelain 2>/dev/null | wc -l', {
|
|
241
|
+
cwd: projectRoot,
|
|
242
|
+
stdio: 'pipe',
|
|
243
|
+
}).toString().trim();
|
|
244
|
+
checks.push({
|
|
245
|
+
category: 'Git',
|
|
246
|
+
name: 'Branch',
|
|
247
|
+
status: 'ok',
|
|
248
|
+
message: branch,
|
|
249
|
+
});
|
|
250
|
+
checks.push({
|
|
251
|
+
category: 'Git',
|
|
252
|
+
name: 'Cambios locales',
|
|
253
|
+
status: parseInt(status) === 0 ? 'ok' : 'warn',
|
|
254
|
+
message: parseInt(status) === 0 ? 'Limpio' : `${status} archivos modificados`,
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
catch { }
|
|
259
|
+
// Display results
|
|
260
|
+
if (opts.json) {
|
|
261
|
+
console.log(JSON.stringify(checks, null, 2));
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
const categories = [...new Set(checks.map(c => c.category))];
|
|
265
|
+
for (const category of categories) {
|
|
266
|
+
const categoryChecks = checks.filter(c => c.category === category);
|
|
267
|
+
console.log(chalk_1.default.bold(` ${category}`));
|
|
268
|
+
console.log(chalk_1.default.dim(' ' + '─'.repeat(50)));
|
|
269
|
+
for (const check of categoryChecks) {
|
|
270
|
+
const icon = check.status === 'ok'
|
|
271
|
+
? chalk_1.default.green('✓')
|
|
272
|
+
: check.status === 'warn'
|
|
273
|
+
? chalk_1.default.yellow('⚠')
|
|
274
|
+
: chalk_1.default.red('✗');
|
|
275
|
+
console.log(` ${icon} ${chalk_1.default.dim(check.name.padEnd(25))} ${check.message}`);
|
|
276
|
+
}
|
|
277
|
+
console.log();
|
|
278
|
+
}
|
|
279
|
+
// Summary
|
|
280
|
+
const errors = checks.filter(c => c.status === 'error');
|
|
281
|
+
const warns = checks.filter(c => c.status === 'warn');
|
|
282
|
+
if (errors.length === 0 && warns.length === 0) {
|
|
283
|
+
console.log(chalk_1.default.bold.green(' Todo esta correcto'));
|
|
284
|
+
}
|
|
285
|
+
else {
|
|
286
|
+
if (errors.length > 0) {
|
|
287
|
+
console.log(chalk_1.default.bold.red(` ${errors.length} error(es) encontrado(s)`));
|
|
288
|
+
}
|
|
289
|
+
if (warns.length > 0) {
|
|
290
|
+
console.log(chalk_1.default.bold.yellow(` ${warns.length} advertencia(s)`));
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
console.log();
|
|
294
|
+
});
|
|
295
|
+
}
|
|
@@ -0,0 +1,249 @@
|
|
|
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.registerInstallQuickCommand = registerInstallQuickCommand;
|
|
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 helpers_1 = require("../utils/helpers");
|
|
16
|
+
const REPO_URL = 'https://github.com/OpenFactu/platform.git';
|
|
17
|
+
function registerInstallQuickCommand(program) {
|
|
18
|
+
program
|
|
19
|
+
.command('install:quick')
|
|
20
|
+
.description('Instalacion rapida de OpenFactu (non-interactive)')
|
|
21
|
+
.option('--tag <tag>', 'Version especifica', 'latest')
|
|
22
|
+
.option('--dir <dir>', 'Directorio de instalacion')
|
|
23
|
+
.option('--no-docker', 'No levantar Docker')
|
|
24
|
+
.option('--monitoring', 'Incluir monitoreo')
|
|
25
|
+
.option('--analytics', 'Incluir analitica completa')
|
|
26
|
+
.option('--service', 'Instalar como servicio systemd')
|
|
27
|
+
.option('--generate-env', 'Generar .env con credenciales seguras')
|
|
28
|
+
.action(async (opts) => {
|
|
29
|
+
console.log();
|
|
30
|
+
console.log(chalk_1.default.bold.white(' OpenFactu — Instalacion Rapida'));
|
|
31
|
+
console.log(chalk_1.default.dim(' ────────────────────────────────────'));
|
|
32
|
+
console.log();
|
|
33
|
+
try {
|
|
34
|
+
let targetDir = opts.dir || path_1.default.join(os_1.default.homedir(), 'openfactu');
|
|
35
|
+
const dockerCmd = (0, helpers_1.getDockerComposeCommand)();
|
|
36
|
+
// Check Docker
|
|
37
|
+
const hasDocker = (() => {
|
|
38
|
+
try {
|
|
39
|
+
(0, child_process_1.execSync)('docker --version', { stdio: 'pipe' });
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
})();
|
|
46
|
+
if (!hasDocker && opts.docker !== false) {
|
|
47
|
+
logger_1.log.error('Docker es requerido para la instalacion rapida');
|
|
48
|
+
logger_1.log.dim(' Instala Docker: https://docs.docker.com/get-docker/');
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
// Clone
|
|
52
|
+
const cloneSpinner = (0, ora_1.default)('Clonando repositorio...').start();
|
|
53
|
+
let ref = opts.tag;
|
|
54
|
+
if (ref === 'latest') {
|
|
55
|
+
try {
|
|
56
|
+
const releases = JSON.parse((0, child_process_1.execSync)('curl -s https://api.github.com/repos/OpenFactu/platform/releases | head -100', { stdio: 'pipe' }).toString());
|
|
57
|
+
const stable = releases.filter((r) => !r.prerelease && !r.draft);
|
|
58
|
+
if (stable.length > 0) {
|
|
59
|
+
ref = stable[0].tag_name;
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
ref = 'main';
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
ref = 'main';
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const isTag = ref.startsWith('v');
|
|
70
|
+
// Verificar si el directorio ya existe
|
|
71
|
+
if (fs_1.default.existsSync(targetDir)) {
|
|
72
|
+
const contents = fs_1.default.readdirSync(targetDir);
|
|
73
|
+
if (contents.length > 0) {
|
|
74
|
+
logger_1.log.warn(`El directorio ${targetDir} ya existe y no esta vacio`);
|
|
75
|
+
const { action } = await inquirer_1.default.prompt([
|
|
76
|
+
{
|
|
77
|
+
type: 'list',
|
|
78
|
+
name: 'action',
|
|
79
|
+
message: 'Que quieres hacer?',
|
|
80
|
+
choices: [
|
|
81
|
+
{ name: 'Sobrescribir (eliminar y reinstalar)', value: 'overwrite' },
|
|
82
|
+
{ name: 'Usar directorio existente (solo configurar)', value: 'reuse' },
|
|
83
|
+
{ name: 'Elegir otro directorio', value: 'newdir' },
|
|
84
|
+
{ name: 'Cancelar', value: 'cancel' },
|
|
85
|
+
],
|
|
86
|
+
},
|
|
87
|
+
]);
|
|
88
|
+
if (action === 'cancel') {
|
|
89
|
+
logger_1.log.info('Instalacion cancelada');
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (action === 'overwrite') {
|
|
93
|
+
const removeSpinner = (0, ora_1.default)('Limpiando directorio...').start();
|
|
94
|
+
(0, child_process_1.execSync)(`rm -rf "${targetDir}"`, { stdio: 'pipe' });
|
|
95
|
+
fs_1.default.mkdirSync(targetDir, { recursive: true });
|
|
96
|
+
removeSpinner.succeed('Directorio limpiado');
|
|
97
|
+
}
|
|
98
|
+
if (action === 'newdir') {
|
|
99
|
+
const { newDir } = await inquirer_1.default.prompt([
|
|
100
|
+
{ type: 'input', name: 'newDir', message: 'Nuevo directorio:', default: path_1.default.join(os_1.default.homedir(), 'openfactu-2') },
|
|
101
|
+
]);
|
|
102
|
+
targetDir = path_1.default.resolve(newDir);
|
|
103
|
+
fs_1.default.mkdirSync(targetDir, { recursive: true });
|
|
104
|
+
}
|
|
105
|
+
if (action === 'reuse') {
|
|
106
|
+
logger_1.log.info('Usando directorio existente');
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
fs_1.default.mkdirSync(targetDir, { recursive: true });
|
|
112
|
+
}
|
|
113
|
+
const cloneCmd = isTag
|
|
114
|
+
? `git clone --depth 1 --branch ${ref} ${REPO_URL} "${targetDir}"`
|
|
115
|
+
: `git clone --branch ${ref} ${REPO_URL} "${targetDir}"`;
|
|
116
|
+
try {
|
|
117
|
+
(0, child_process_1.execSync)(cloneCmd, { stdio: 'pipe', timeout: 120000 });
|
|
118
|
+
cloneSpinner.succeed('Repositorio clonado');
|
|
119
|
+
}
|
|
120
|
+
catch (err) {
|
|
121
|
+
if (err.message?.includes('ya existe') || err.message?.includes('already exists')) {
|
|
122
|
+
cloneSpinner.warn('Directorio ya existe, usando existente');
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
cloneSpinner.fail('Error al clonar: ' + err.message);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
// Generate .env
|
|
130
|
+
if (opts.generateEnv) {
|
|
131
|
+
const envSpinner = (0, ora_1.default)('Generando configuracion...').start();
|
|
132
|
+
const dbPassword = (0, helpers_1.generatePassword)(24);
|
|
133
|
+
const jwtSecret = (0, helpers_1.generatePassword)(48);
|
|
134
|
+
const envContent = `POSTGRES_USER=openfactu
|
|
135
|
+
POSTGRES_PASSWORD=${dbPassword}
|
|
136
|
+
POSTGRES_DB=openfactudb
|
|
137
|
+
DATABASE_URL=postgresql://openfactu:${encodeURIComponent(dbPassword)}@db:5432/openfactudb
|
|
138
|
+
SERVER_PORT=3000
|
|
139
|
+
WEB_PORT=8080
|
|
140
|
+
DB_PORT=5432
|
|
141
|
+
JWT_SECRET=${jwtSecret}
|
|
142
|
+
SESSION_SECRET=${(0, helpers_1.generatePassword)(32)}
|
|
143
|
+
NODE_ENV=production
|
|
144
|
+
HOST=localhost
|
|
145
|
+
CORS_ORIGIN=http://localhost:8080
|
|
146
|
+
VITE_API_URL=http://localhost:3000
|
|
147
|
+
ADMIN_EMAIL=admin@openfactu.local
|
|
148
|
+
ADMIN_PASSWORD=${(0, helpers_1.generatePassword)(16)}
|
|
149
|
+
`;
|
|
150
|
+
fs_1.default.writeFileSync(path_1.default.join(targetDir, '.env'), envContent);
|
|
151
|
+
envSpinner.succeed('.env generado');
|
|
152
|
+
logger_1.log.blank();
|
|
153
|
+
logger_1.log.info(`${chalk_1.default.dim('Admin:')} admin@openfactu.local / ${chalk_1.default.yellow('ver .env')}`);
|
|
154
|
+
logger_1.log.blank();
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
const envExample = path_1.default.join(targetDir, '.env.example');
|
|
158
|
+
const envFile = path_1.default.join(targetDir, '.env');
|
|
159
|
+
if (fs_1.default.existsSync(envExample) && !fs_1.default.existsSync(envFile)) {
|
|
160
|
+
fs_1.default.copyFileSync(envExample, envFile);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
// Docker build and up
|
|
164
|
+
if (opts.docker !== false) {
|
|
165
|
+
const buildSpinner = (0, ora_1.default)('Construyendo contenedores...').start();
|
|
166
|
+
try {
|
|
167
|
+
(0, child_process_1.execSync)(`${dockerCmd} build`, { cwd: targetDir, stdio: 'pipe', timeout: 300000 });
|
|
168
|
+
buildSpinner.succeed('Contenedores construidos');
|
|
169
|
+
}
|
|
170
|
+
catch (err) {
|
|
171
|
+
buildSpinner.fail('Error en build');
|
|
172
|
+
logger_1.log.dim(` cd ${targetDir} && ${dockerCmd} build`);
|
|
173
|
+
}
|
|
174
|
+
const upSpinner = (0, ora_1.default)('Levantando servicios...').start();
|
|
175
|
+
try {
|
|
176
|
+
let composeFlags = '-f docker-compose.yml';
|
|
177
|
+
if (fs_1.default.existsSync(path_1.default.join(targetDir, 'docker-compose.prod.yml'))) {
|
|
178
|
+
composeFlags = '-f docker-compose.prod.yml';
|
|
179
|
+
}
|
|
180
|
+
if (opts.monitoring) {
|
|
181
|
+
const monPath = path_1.default.join(targetDir, 'docker-compose.monitoring.yml');
|
|
182
|
+
if (fs_1.default.existsSync(monPath)) {
|
|
183
|
+
composeFlags += ' -f docker-compose.monitoring.yml';
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
(0, child_process_1.execSync)(`${dockerCmd} ${composeFlags} up -d`, {
|
|
187
|
+
cwd: targetDir,
|
|
188
|
+
stdio: 'pipe',
|
|
189
|
+
timeout: 120000,
|
|
190
|
+
});
|
|
191
|
+
upSpinner.succeed('Servicios levantados');
|
|
192
|
+
}
|
|
193
|
+
catch (err) {
|
|
194
|
+
upSpinner.fail('Error: ' + err.message);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
// Install as service
|
|
198
|
+
if (opts.service) {
|
|
199
|
+
const svcSpinner = (0, ora_1.default)('Instalando servicio systemd...').start();
|
|
200
|
+
try {
|
|
201
|
+
const unitContent = `[Unit]
|
|
202
|
+
Description=OpenFactu ERP Platform
|
|
203
|
+
After=docker.service network-online.target
|
|
204
|
+
Requires=docker.service
|
|
205
|
+
|
|
206
|
+
[Service]
|
|
207
|
+
Type=oneshot
|
|
208
|
+
RemainAfterExit=yes
|
|
209
|
+
WorkingDirectory=${targetDir}
|
|
210
|
+
ExecStart=${dockerCmd} -f docker-compose.yml up -d
|
|
211
|
+
ExecStop=${dockerCmd} -f docker-compose.yml down
|
|
212
|
+
Restart=on-failure
|
|
213
|
+
RestartSec=30
|
|
214
|
+
|
|
215
|
+
[Install]
|
|
216
|
+
WantedBy=multi-user.target
|
|
217
|
+
`;
|
|
218
|
+
const tempPath = '/tmp/openfactu.service';
|
|
219
|
+
const unitPath = '/etc/systemd/system/openfactu.service';
|
|
220
|
+
fs_1.default.writeFileSync(tempPath, unitContent);
|
|
221
|
+
(0, child_process_1.execSync)(`sudo mv ${tempPath} ${unitPath}`, { stdio: 'pipe' });
|
|
222
|
+
(0, child_process_1.execSync)('sudo systemctl daemon-reload', { stdio: 'pipe' });
|
|
223
|
+
(0, child_process_1.execSync)('sudo systemctl enable openfactu', { stdio: 'pipe' });
|
|
224
|
+
svcSpinner.succeed('Servicio instalado');
|
|
225
|
+
}
|
|
226
|
+
catch (err) {
|
|
227
|
+
svcSpinner.fail('No se pudo instalar el servicio');
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
// Summary
|
|
231
|
+
logger_1.log.blank();
|
|
232
|
+
console.log(chalk_1.default.bold.green(' Instalacion rapida completada'));
|
|
233
|
+
console.log(chalk_1.default.dim(' ────────────────────────────────────'));
|
|
234
|
+
logger_1.log.info(`Directorio: ${chalk_1.default.cyan(targetDir)}`);
|
|
235
|
+
logger_1.log.info(`Version: ${chalk_1.default.cyan(ref)}`);
|
|
236
|
+
logger_1.log.blank();
|
|
237
|
+
logger_1.log.dim(' Comandos utiles:');
|
|
238
|
+
logger_1.log.dim(` cd ${targetDir}`);
|
|
239
|
+
logger_1.log.dim(' openfactu setup');
|
|
240
|
+
logger_1.log.dim(' openfactu deploy');
|
|
241
|
+
logger_1.log.dim(' openfactu doctor');
|
|
242
|
+
logger_1.log.blank();
|
|
243
|
+
}
|
|
244
|
+
catch (err) {
|
|
245
|
+
logger_1.log.error(err.message);
|
|
246
|
+
process.exitCode = 1;
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
}
|