@openfactu/cli 0.0.2
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/dist/bin/openfactu.d.ts +2 -0
- package/dist/bin/openfactu.js +23 -0
- package/dist/src/commands/migrate.d.ts +2 -0
- package/dist/src/commands/migrate.js +160 -0
- package/dist/src/commands/plugin.d.ts +2 -0
- package/dist/src/commands/plugin.js +95 -0
- package/dist/src/commands/setup.d.ts +2 -0
- package/dist/src/commands/setup.js +152 -0
- package/dist/src/commands/tenant.d.ts +2 -0
- package/dist/src/commands/tenant.js +220 -0
- package/dist/src/commands/update.d.ts +2 -0
- package/dist/src/commands/update.js +282 -0
- package/dist/src/commands/version.d.ts +2 -0
- package/dist/src/commands/version.js +49 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +24 -0
- package/dist/src/utils/config.d.ts +8 -0
- package/dist/src/utils/config.js +23 -0
- package/dist/src/utils/db.d.ts +12 -0
- package/dist/src/utils/db.js +65 -0
- package/dist/src/utils/logger.d.ts +9 -0
- package/dist/src/utils/logger.js +16 -0
- package/dist/src/utils/paths.d.ts +18 -0
- package/dist/src/utils/paths.js +84 -0
- package/package.json +39 -0
|
@@ -0,0 +1,282 @@
|
|
|
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.registerUpdateCommand = registerUpdateCommand;
|
|
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 logger_1 = require("../utils/logger");
|
|
14
|
+
const paths_1 = require("../utils/paths");
|
|
15
|
+
const ROOT_DIR = (0, paths_1.getProjectRoot)();
|
|
16
|
+
const BACKUP_DIRS = ['storage', 'plugins', '.env'];
|
|
17
|
+
const SAFE_DIRS = ['storage', 'plugins', 'node_modules', '.env', '.git'];
|
|
18
|
+
function getCurrentVersion() {
|
|
19
|
+
try {
|
|
20
|
+
const pkg = JSON.parse(fs_1.default.readFileSync(path_1.default.join(ROOT_DIR, 'package.json'), 'utf-8'));
|
|
21
|
+
return pkg.version || '0.0.0';
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return '0.0.0';
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function getCurrentBranch() {
|
|
28
|
+
try {
|
|
29
|
+
return (0, child_process_1.execSync)('git rev-parse --abbrev-ref HEAD', { cwd: ROOT_DIR }).toString().trim();
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return 'unknown';
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function getCurrentCommit() {
|
|
36
|
+
try {
|
|
37
|
+
return (0, child_process_1.execSync)('git rev-parse --short HEAD', { cwd: ROOT_DIR }).toString().trim();
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return 'unknown';
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function hasUncommittedChanges() {
|
|
44
|
+
try {
|
|
45
|
+
const output = (0, child_process_1.execSync)('git status --porcelain', { cwd: ROOT_DIR }).toString().trim();
|
|
46
|
+
return output.length > 0;
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function getRemoteTags() {
|
|
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();
|
|
56
|
+
return output ? output.split('\n') : [];
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return [];
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function getRemoteLatestCommit(branch) {
|
|
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();
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return 'unknown';
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function registerUpdateCommand(program) {
|
|
72
|
+
// ── openfactu update ──
|
|
73
|
+
program
|
|
74
|
+
.command('update')
|
|
75
|
+
.description('Actualiza OpenFactu a la última versión')
|
|
76
|
+
.option('--branch <branch>', 'Branch específica (default: main)')
|
|
77
|
+
.option('--tag <tag>', 'Tag/versión específica (ej: v1.2.0)')
|
|
78
|
+
.option('--force', 'Forzar actualización sin confirmación')
|
|
79
|
+
.action(async (opts) => {
|
|
80
|
+
console.log();
|
|
81
|
+
console.log(chalk_1.default.bold.white(' OpenFactu — Actualización'));
|
|
82
|
+
console.log(chalk_1.default.dim(' ────────────────────────────────────'));
|
|
83
|
+
console.log();
|
|
84
|
+
try {
|
|
85
|
+
const currentVersion = getCurrentVersion();
|
|
86
|
+
const currentBranch = getCurrentBranch();
|
|
87
|
+
const currentCommit = getCurrentCommit();
|
|
88
|
+
logger_1.log.info(`Versión actual: ${chalk_1.default.cyan(currentVersion)}`);
|
|
89
|
+
logger_1.log.info(`Branch: ${chalk_1.default.cyan(currentBranch)}`);
|
|
90
|
+
logger_1.log.info(`Commit: ${chalk_1.default.cyan(currentCommit)}`);
|
|
91
|
+
logger_1.log.blank();
|
|
92
|
+
// 1. Verificar cambios sin commitear
|
|
93
|
+
if (hasUncommittedChanges()) {
|
|
94
|
+
logger_1.log.warn('Hay cambios sin commitear en el repositorio');
|
|
95
|
+
if (!opts.force) {
|
|
96
|
+
const { proceed } = await inquirer_1.default.prompt([
|
|
97
|
+
{
|
|
98
|
+
type: 'confirm',
|
|
99
|
+
name: 'proceed',
|
|
100
|
+
message: 'Hay cambios locales. ¿Continuar? (se hará stash automático)',
|
|
101
|
+
default: false,
|
|
102
|
+
},
|
|
103
|
+
]);
|
|
104
|
+
if (!proceed) {
|
|
105
|
+
logger_1.log.info('Actualización cancelada');
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// Guardar cambios locales
|
|
110
|
+
const stashSpinner = (0, ora_1.default)('Guardando cambios locales (git stash)...').start();
|
|
111
|
+
try {
|
|
112
|
+
(0, child_process_1.execSync)('git stash push -m "openfactu-cli-update-backup"', { cwd: ROOT_DIR, stdio: 'pipe' });
|
|
113
|
+
stashSpinner.succeed('Cambios locales guardados en stash');
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
stashSpinner.warn('No se pudieron guardar cambios: ' + err.message);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// 2. Fetch remoto
|
|
120
|
+
const fetchSpinner = (0, ora_1.default)('Descargando información del repositorio...').start();
|
|
121
|
+
try {
|
|
122
|
+
(0, child_process_1.execSync)('git fetch --all --tags', { cwd: ROOT_DIR, stdio: 'pipe' });
|
|
123
|
+
fetchSpinner.succeed('Repositorio actualizado');
|
|
124
|
+
}
|
|
125
|
+
catch (err) {
|
|
126
|
+
fetchSpinner.fail('No se pudo conectar al repositorio remoto: ' + err.message);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
// 3. Determinar versión objetivo
|
|
130
|
+
let target;
|
|
131
|
+
if (opts.tag) {
|
|
132
|
+
// Actualizar a un tag específico
|
|
133
|
+
target = opts.tag;
|
|
134
|
+
logger_1.log.info(`Actualizando al tag: ${chalk_1.default.bold(target)}`);
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
const branch = opts.branch || currentBranch || 'main';
|
|
138
|
+
const remoteCommit = getRemoteLatestCommit(branch);
|
|
139
|
+
if (remoteCommit === currentCommit) {
|
|
140
|
+
logger_1.log.success('Ya estás en la última versión');
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
// Mostrar commits pendientes
|
|
144
|
+
try {
|
|
145
|
+
const behindCount = (0, child_process_1.execSync)(`git rev-list --count HEAD..origin/${branch}`, { cwd: ROOT_DIR }).toString().trim();
|
|
146
|
+
logger_1.log.info(`Commits nuevos disponibles: ${chalk_1.default.yellow(behindCount)}`);
|
|
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();
|
|
149
|
+
if (changelog) {
|
|
150
|
+
logger_1.log.blank();
|
|
151
|
+
logger_1.log.dim(' Cambios recientes:');
|
|
152
|
+
for (const line of changelog.split('\n')) {
|
|
153
|
+
logger_1.log.dim(` ${line}`);
|
|
154
|
+
}
|
|
155
|
+
logger_1.log.blank();
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
catch { }
|
|
159
|
+
target = `origin/${branch}`;
|
|
160
|
+
}
|
|
161
|
+
// 4. Confirmación
|
|
162
|
+
if (!opts.force) {
|
|
163
|
+
const { confirm } = await inquirer_1.default.prompt([
|
|
164
|
+
{
|
|
165
|
+
type: 'confirm',
|
|
166
|
+
name: 'confirm',
|
|
167
|
+
message: `¿Actualizar a ${target}?`,
|
|
168
|
+
default: true,
|
|
169
|
+
},
|
|
170
|
+
]);
|
|
171
|
+
if (!confirm) {
|
|
172
|
+
logger_1.log.info('Actualización cancelada');
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
// 5. Backup de configuración
|
|
177
|
+
const backupSpinner = (0, ora_1.default)('Verificando archivos protegidos...').start();
|
|
178
|
+
const protectedFiles = [];
|
|
179
|
+
for (const item of BACKUP_DIRS) {
|
|
180
|
+
const itemPath = path_1.default.join(ROOT_DIR, item);
|
|
181
|
+
if (fs_1.default.existsSync(itemPath)) {
|
|
182
|
+
protectedFiles.push(item);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
backupSpinner.succeed(`Archivos protegidos: ${protectedFiles.join(', ') || 'ninguno'}`);
|
|
186
|
+
// 6. Aplicar actualización
|
|
187
|
+
const updateSpinner = (0, ora_1.default)('Aplicando actualización...').start();
|
|
188
|
+
try {
|
|
189
|
+
if (opts.tag) {
|
|
190
|
+
(0, child_process_1.execSync)(`git checkout ${opts.tag}`, { cwd: ROOT_DIR, stdio: 'pipe' });
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
const branch = opts.branch || currentBranch || 'main';
|
|
194
|
+
(0, child_process_1.execSync)(`git pull origin ${branch} --ff-only`, { cwd: ROOT_DIR, stdio: 'pipe' });
|
|
195
|
+
}
|
|
196
|
+
updateSpinner.succeed('Código actualizado');
|
|
197
|
+
}
|
|
198
|
+
catch (err) {
|
|
199
|
+
updateSpinner.fail('Error al actualizar: ' + err.message);
|
|
200
|
+
logger_1.log.warn('Intentando merge...');
|
|
201
|
+
try {
|
|
202
|
+
const branch = opts.branch || currentBranch || 'main';
|
|
203
|
+
(0, child_process_1.execSync)(`git pull origin ${branch}`, { cwd: ROOT_DIR, stdio: 'pipe' });
|
|
204
|
+
logger_1.log.success('Merge completado');
|
|
205
|
+
}
|
|
206
|
+
catch (mergeErr) {
|
|
207
|
+
logger_1.log.error('Conflicto de merge. Resuelve manualmente con:');
|
|
208
|
+
logger_1.log.dim(' git status');
|
|
209
|
+
logger_1.log.dim(' git merge --abort (para cancelar)');
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
// 7. Instalar dependencias
|
|
214
|
+
const depsSpinner = (0, ora_1.default)('Instalando dependencias...').start();
|
|
215
|
+
try {
|
|
216
|
+
(0, child_process_1.execSync)('npm install', { cwd: ROOT_DIR, stdio: 'pipe', timeout: 120000 });
|
|
217
|
+
depsSpinner.succeed('Dependencias instaladas');
|
|
218
|
+
}
|
|
219
|
+
catch (err) {
|
|
220
|
+
depsSpinner.warn('Error instalando dependencias: ' + err.message);
|
|
221
|
+
}
|
|
222
|
+
// 8. Mostrar nueva versión
|
|
223
|
+
const newVersion = getCurrentVersion();
|
|
224
|
+
const newCommit = getCurrentCommit();
|
|
225
|
+
logger_1.log.blank();
|
|
226
|
+
logger_1.log.success(chalk_1.default.bold('Actualización completada'));
|
|
227
|
+
logger_1.log.blank();
|
|
228
|
+
logger_1.log.info(`Versión: ${chalk_1.default.dim(currentVersion)} ${chalk_1.default.white('→')} ${chalk_1.default.cyan(newVersion)}`);
|
|
229
|
+
logger_1.log.info(`Commit: ${chalk_1.default.dim(currentCommit)} ${chalk_1.default.white('→')} ${chalk_1.default.cyan(newCommit)}`);
|
|
230
|
+
logger_1.log.blank();
|
|
231
|
+
logger_1.log.dim(' Próximos pasos:');
|
|
232
|
+
logger_1.log.dim(' openfactu migrate — Aplicar migraciones de BD pendientes');
|
|
233
|
+
logger_1.log.dim(' openfactu migrate:status — Ver estado de migraciones');
|
|
234
|
+
logger_1.log.blank();
|
|
235
|
+
}
|
|
236
|
+
catch (err) {
|
|
237
|
+
logger_1.log.error(err.message);
|
|
238
|
+
process.exitCode = 1;
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
// ── openfactu update:check ──
|
|
242
|
+
program
|
|
243
|
+
.command('update:check')
|
|
244
|
+
.description('Comprueba si hay actualizaciones disponibles')
|
|
245
|
+
.action(async () => {
|
|
246
|
+
const spinner = (0, ora_1.default)('Comprobando actualizaciones...').start();
|
|
247
|
+
try {
|
|
248
|
+
const currentVersion = getCurrentVersion();
|
|
249
|
+
const currentBranch = getCurrentBranch();
|
|
250
|
+
const currentCommit = getCurrentCommit();
|
|
251
|
+
(0, child_process_1.execSync)('git fetch --all --tags', { cwd: ROOT_DIR, stdio: 'pipe' });
|
|
252
|
+
const remoteCommit = getRemoteLatestCommit(currentBranch);
|
|
253
|
+
const tags = getRemoteTags();
|
|
254
|
+
spinner.stop();
|
|
255
|
+
logger_1.log.info(`Versión actual: ${chalk_1.default.cyan(currentVersion)} (${currentCommit})`);
|
|
256
|
+
logger_1.log.info(`Branch: ${chalk_1.default.cyan(currentBranch)}`);
|
|
257
|
+
if (remoteCommit === currentCommit) {
|
|
258
|
+
logger_1.log.success('Estás en la última versión');
|
|
259
|
+
}
|
|
260
|
+
else {
|
|
261
|
+
const behindCount = (0, child_process_1.execSync)(`git rev-list --count HEAD..origin/${currentBranch}`, { cwd: ROOT_DIR }).toString().trim();
|
|
262
|
+
logger_1.log.warn(`Hay ${chalk_1.default.yellow(behindCount)} commit(s) nuevos disponibles`);
|
|
263
|
+
logger_1.log.dim(` Ejecuta: openfactu update`);
|
|
264
|
+
}
|
|
265
|
+
if (tags.length > 0) {
|
|
266
|
+
logger_1.log.blank();
|
|
267
|
+
logger_1.log.info('Versiones disponibles (tags):');
|
|
268
|
+
for (const tag of tags.slice(0, 5)) {
|
|
269
|
+
const isCurrent = tag === `v${currentVersion}`;
|
|
270
|
+
console.log(` ${isCurrent ? chalk_1.default.green('→') : ' '} ${chalk_1.default.cyan(tag)}${isCurrent ? chalk_1.default.dim(' (actual)') : ''}`);
|
|
271
|
+
}
|
|
272
|
+
if (tags.length > 5) {
|
|
273
|
+
logger_1.log.dim(` ... y ${tags.length - 5} más`);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
catch (err) {
|
|
278
|
+
spinner.fail('No se pudo comprobar: ' + err.message);
|
|
279
|
+
process.exitCode = 1;
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
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.registerVersionCommand = registerVersionCommand;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const fs_1 = __importDefault(require("fs"));
|
|
10
|
+
const paths_1 = require("../utils/paths");
|
|
11
|
+
function registerVersionCommand(program) {
|
|
12
|
+
program
|
|
13
|
+
.command('version')
|
|
14
|
+
.description('Muestra la versión de OpenFactu')
|
|
15
|
+
.action(() => {
|
|
16
|
+
let root;
|
|
17
|
+
try {
|
|
18
|
+
root = (0, paths_1.getProjectRoot)();
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
root = '';
|
|
22
|
+
}
|
|
23
|
+
const cliPkg = readPkg(path_1.default.resolve(__dirname, '../../package.json'));
|
|
24
|
+
const serverPkg = root ? readPkg(path_1.default.join(root, 'apps/server/package.json')) : null;
|
|
25
|
+
const webPkg = root ? readPkg(path_1.default.join(root, 'apps/web/package.json')) : null;
|
|
26
|
+
const rootPkg = root ? readPkg(path_1.default.join(root, 'package.json')) : null;
|
|
27
|
+
console.log();
|
|
28
|
+
console.log(chalk_1.default.bold.white(' OpenFactu'));
|
|
29
|
+
console.log(chalk_1.default.dim(' ─────────────────────────────'));
|
|
30
|
+
console.log(` ${chalk_1.default.dim('CLI:')} ${chalk_1.default.cyan(cliPkg?.version || '?')}`);
|
|
31
|
+
console.log(` ${chalk_1.default.dim('Server:')} ${chalk_1.default.cyan(serverPkg?.version || '?')}`);
|
|
32
|
+
console.log(` ${chalk_1.default.dim('Web:')} ${chalk_1.default.cyan(webPkg?.version || '?')}`);
|
|
33
|
+
console.log(` ${chalk_1.default.dim('Root:')} ${chalk_1.default.cyan(rootPkg?.version || '?')}`);
|
|
34
|
+
console.log(` ${chalk_1.default.dim('Node:')} ${chalk_1.default.cyan(process.version)}`);
|
|
35
|
+
if (root) {
|
|
36
|
+
console.log(` ${chalk_1.default.dim('Path:')} ${chalk_1.default.dim(root)}`);
|
|
37
|
+
}
|
|
38
|
+
console.log();
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
function readPkg(pkgPath) {
|
|
42
|
+
try {
|
|
43
|
+
if (fs_1.default.existsSync(pkgPath)) {
|
|
44
|
+
return JSON.parse(fs_1.default.readFileSync(pkgPath, 'utf-8'));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
catch { }
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createCLI = createCLI;
|
|
4
|
+
const commander_1 = require("commander");
|
|
5
|
+
const version_1 = require("./commands/version");
|
|
6
|
+
const migrate_1 = require("./commands/migrate");
|
|
7
|
+
const tenant_1 = require("./commands/tenant");
|
|
8
|
+
const plugin_1 = require("./commands/plugin");
|
|
9
|
+
const setup_1 = require("./commands/setup");
|
|
10
|
+
const update_1 = require("./commands/update");
|
|
11
|
+
function createCLI() {
|
|
12
|
+
const program = new commander_1.Command();
|
|
13
|
+
program
|
|
14
|
+
.name('openfactu')
|
|
15
|
+
.description('CLI para gestionar OpenFactu')
|
|
16
|
+
.version('0.1.0');
|
|
17
|
+
(0, version_1.registerVersionCommand)(program);
|
|
18
|
+
(0, migrate_1.registerMigrateCommand)(program);
|
|
19
|
+
(0, tenant_1.registerTenantCommand)(program);
|
|
20
|
+
(0, plugin_1.registerPluginCommand)(program);
|
|
21
|
+
(0, setup_1.registerSetupCommand)(program);
|
|
22
|
+
(0, update_1.registerUpdateCommand)(program);
|
|
23
|
+
return program;
|
|
24
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
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.loadConfig = loadConfig;
|
|
7
|
+
const dotenv_1 = __importDefault(require("dotenv"));
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const paths_1 = require("./paths");
|
|
10
|
+
/**
|
|
11
|
+
* Carga la configuración desde .env del proyecto OpenFactu.
|
|
12
|
+
*/
|
|
13
|
+
function loadConfig() {
|
|
14
|
+
const envPath = (0, paths_1.getEnvPath)();
|
|
15
|
+
if (fs_1.default.existsSync(envPath)) {
|
|
16
|
+
dotenv_1.default.config({ path: envPath });
|
|
17
|
+
}
|
|
18
|
+
return {
|
|
19
|
+
databaseUrl: process.env.DATABASE_URL || 'postgresql://openfactu:openfactu_pass@localhost:5432/openfactudb',
|
|
20
|
+
jwtSecret: process.env.JWT_SECRET || 'super-secret-key',
|
|
21
|
+
serverPort: process.env.SERVER_PORT || '3000',
|
|
22
|
+
};
|
|
23
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Pool } from 'pg';
|
|
2
|
+
import { eq, sql } from 'drizzle-orm';
|
|
3
|
+
declare const schema: any;
|
|
4
|
+
export declare function getPublicDb(): any;
|
|
5
|
+
export declare function getTenantDb(schemaName: string): import("drizzle-orm/node-postgres").NodePgDatabase<any> & {
|
|
6
|
+
$client: Pool;
|
|
7
|
+
};
|
|
8
|
+
export declare function getAllTenants(): Promise<any>;
|
|
9
|
+
export declare function getTenantByName(name: string): Promise<any>;
|
|
10
|
+
export declare function testConnection(): Promise<boolean>;
|
|
11
|
+
export declare function disconnect(): Promise<void>;
|
|
12
|
+
export { schema, sql, eq };
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.eq = exports.sql = exports.schema = void 0;
|
|
4
|
+
exports.getPublicDb = getPublicDb;
|
|
5
|
+
exports.getTenantDb = getTenantDb;
|
|
6
|
+
exports.getAllTenants = getAllTenants;
|
|
7
|
+
exports.getTenantByName = getTenantByName;
|
|
8
|
+
exports.testConnection = testConnection;
|
|
9
|
+
exports.disconnect = disconnect;
|
|
10
|
+
const node_postgres_1 = require("drizzle-orm/node-postgres");
|
|
11
|
+
const pg_1 = require("pg");
|
|
12
|
+
const drizzle_orm_1 = require("drizzle-orm");
|
|
13
|
+
Object.defineProperty(exports, "eq", { enumerable: true, get: function () { return drizzle_orm_1.eq; } });
|
|
14
|
+
Object.defineProperty(exports, "sql", { enumerable: true, get: function () { return drizzle_orm_1.sql; } });
|
|
15
|
+
const config_1 = require("./config");
|
|
16
|
+
// Importar schema de forma dinámica según la ruta del proyecto
|
|
17
|
+
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
|
+
let pool = null;
|
|
22
|
+
let db = null;
|
|
23
|
+
function getPublicDb() {
|
|
24
|
+
if (db)
|
|
25
|
+
return db;
|
|
26
|
+
const config = (0, config_1.loadConfig)();
|
|
27
|
+
pool = new pg_1.Pool({ connectionString: config.databaseUrl });
|
|
28
|
+
db = (0, node_postgres_1.drizzle)(pool, { schema });
|
|
29
|
+
return db;
|
|
30
|
+
}
|
|
31
|
+
function getTenantDb(schemaName) {
|
|
32
|
+
const config = (0, config_1.loadConfig)();
|
|
33
|
+
const url = `${config.databaseUrl}${config.databaseUrl.includes('?') ? '&' : '?'}options=-csearch_path%3D${schemaName}%2Cpublic`;
|
|
34
|
+
const tenantPool = new pg_1.Pool({ connectionString: url });
|
|
35
|
+
return (0, node_postgres_1.drizzle)(tenantPool, { schema });
|
|
36
|
+
}
|
|
37
|
+
async function getAllTenants() {
|
|
38
|
+
const publicDb = getPublicDb();
|
|
39
|
+
return publicDb.select().from(schema.tenants);
|
|
40
|
+
}
|
|
41
|
+
async function getTenantByName(name) {
|
|
42
|
+
const publicDb = getPublicDb();
|
|
43
|
+
const [tenant] = await publicDb
|
|
44
|
+
.select()
|
|
45
|
+
.from(schema.tenants)
|
|
46
|
+
.where((0, drizzle_orm_1.eq)(schema.tenants.name, name));
|
|
47
|
+
return tenant || null;
|
|
48
|
+
}
|
|
49
|
+
async function testConnection() {
|
|
50
|
+
try {
|
|
51
|
+
const publicDb = getPublicDb();
|
|
52
|
+
await publicDb.execute(drizzle_orm_1.sql.raw('SELECT 1'));
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
async function disconnect() {
|
|
60
|
+
if (pool) {
|
|
61
|
+
await pool.end();
|
|
62
|
+
pool = null;
|
|
63
|
+
db = null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
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.log = void 0;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
exports.log = {
|
|
9
|
+
info: (msg) => console.log(chalk_1.default.blue('i'), msg),
|
|
10
|
+
success: (msg) => console.log(chalk_1.default.green('✓'), msg),
|
|
11
|
+
warn: (msg) => console.log(chalk_1.default.yellow('!'), msg),
|
|
12
|
+
error: (msg) => console.log(chalk_1.default.red('✗'), msg),
|
|
13
|
+
dim: (msg) => console.log(chalk_1.default.dim(msg)),
|
|
14
|
+
title: (msg) => console.log('\n' + chalk_1.default.bold.white(msg)),
|
|
15
|
+
blank: () => console.log(),
|
|
16
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detecta la raíz del proyecto OpenFactu.
|
|
3
|
+
* Busca en este orden:
|
|
4
|
+
* 1. Variable de entorno OPENFACTU_HOME
|
|
5
|
+
* 2. El directorio actual (si contiene package.json con name "openfactu")
|
|
6
|
+
* 3. Hacia arriba desde el directorio actual
|
|
7
|
+
* 4. Ruta relativa desde el paquete CLI instalado (monorepo)
|
|
8
|
+
*/
|
|
9
|
+
export declare function getProjectRoot(): string;
|
|
10
|
+
/**
|
|
11
|
+
* Setea manualmente la raíz del proyecto.
|
|
12
|
+
*/
|
|
13
|
+
export declare function setProjectRoot(root: string): void;
|
|
14
|
+
export declare function getServerDir(): string;
|
|
15
|
+
export declare function getServerSrcDir(): string;
|
|
16
|
+
export declare function getMigrationsDir(): string;
|
|
17
|
+
export declare function getPluginsDir(): string;
|
|
18
|
+
export declare function getEnvPath(): string;
|
|
@@ -0,0 +1,84 @@
|
|
|
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.getProjectRoot = getProjectRoot;
|
|
7
|
+
exports.setProjectRoot = setProjectRoot;
|
|
8
|
+
exports.getServerDir = getServerDir;
|
|
9
|
+
exports.getServerSrcDir = getServerSrcDir;
|
|
10
|
+
exports.getMigrationsDir = getMigrationsDir;
|
|
11
|
+
exports.getPluginsDir = getPluginsDir;
|
|
12
|
+
exports.getEnvPath = getEnvPath;
|
|
13
|
+
const path_1 = __importDefault(require("path"));
|
|
14
|
+
const fs_1 = __importDefault(require("fs"));
|
|
15
|
+
let _projectRoot = null;
|
|
16
|
+
/**
|
|
17
|
+
* Detecta la raíz del proyecto OpenFactu.
|
|
18
|
+
* Busca en este orden:
|
|
19
|
+
* 1. Variable de entorno OPENFACTU_HOME
|
|
20
|
+
* 2. El directorio actual (si contiene package.json con name "openfactu")
|
|
21
|
+
* 3. Hacia arriba desde el directorio actual
|
|
22
|
+
* 4. Ruta relativa desde el paquete CLI instalado (monorepo)
|
|
23
|
+
*/
|
|
24
|
+
function getProjectRoot() {
|
|
25
|
+
if (_projectRoot)
|
|
26
|
+
return _projectRoot;
|
|
27
|
+
// 1. Variable de entorno
|
|
28
|
+
if (process.env.OPENFACTU_HOME) {
|
|
29
|
+
const envPath = path_1.default.resolve(process.env.OPENFACTU_HOME);
|
|
30
|
+
if (isOpenFactuRoot(envPath)) {
|
|
31
|
+
_projectRoot = envPath;
|
|
32
|
+
return _projectRoot;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
// 2. Directorio actual
|
|
36
|
+
if (isOpenFactuRoot(process.cwd())) {
|
|
37
|
+
_projectRoot = process.cwd();
|
|
38
|
+
return _projectRoot;
|
|
39
|
+
}
|
|
40
|
+
// 3. Buscar hacia arriba desde el cwd
|
|
41
|
+
let dir = process.cwd();
|
|
42
|
+
while (dir !== path_1.default.dirname(dir)) {
|
|
43
|
+
if (isOpenFactuRoot(dir)) {
|
|
44
|
+
_projectRoot = dir;
|
|
45
|
+
return _projectRoot;
|
|
46
|
+
}
|
|
47
|
+
dir = path_1.default.dirname(dir);
|
|
48
|
+
}
|
|
49
|
+
// 4. Relativo al paquete CLI (dentro del monorepo)
|
|
50
|
+
const monorepoRoot = path_1.default.resolve(__dirname, '../../../..');
|
|
51
|
+
if (isOpenFactuRoot(monorepoRoot)) {
|
|
52
|
+
_projectRoot = monorepoRoot;
|
|
53
|
+
return _projectRoot;
|
|
54
|
+
}
|
|
55
|
+
throw new Error('No se encontró la instalación de OpenFactu.\n' +
|
|
56
|
+
'Opciones:\n' +
|
|
57
|
+
' 1. Ejecuta el comando desde el directorio de OpenFactu\n' +
|
|
58
|
+
' 2. Configura OPENFACTU_HOME=/ruta/a/OpenFactu\n');
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Setea manualmente la raíz del proyecto.
|
|
62
|
+
*/
|
|
63
|
+
function setProjectRoot(root) {
|
|
64
|
+
_projectRoot = root;
|
|
65
|
+
}
|
|
66
|
+
function isOpenFactuRoot(dir) {
|
|
67
|
+
const pkgPath = path_1.default.join(dir, 'package.json');
|
|
68
|
+
if (!fs_1.default.existsSync(pkgPath))
|
|
69
|
+
return false;
|
|
70
|
+
try {
|
|
71
|
+
const pkg = JSON.parse(fs_1.default.readFileSync(pkgPath, 'utf-8'));
|
|
72
|
+
// Detectar por nombre o por la estructura de workspaces
|
|
73
|
+
return pkg.name === 'openfactu' || (pkg.workspaces && fs_1.default.existsSync(path_1.default.join(dir, 'apps/server')));
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// Paths derivados
|
|
80
|
+
function getServerDir() { return path_1.default.join(getProjectRoot(), 'apps/server'); }
|
|
81
|
+
function getServerSrcDir() { return path_1.default.join(getServerDir(), 'src'); }
|
|
82
|
+
function getMigrationsDir() { return path_1.default.join(getServerSrcDir(), 'core/tenant/migrations'); }
|
|
83
|
+
function getPluginsDir() { return path_1.default.join(getProjectRoot(), 'plugins'); }
|
|
84
|
+
function getEnvPath() { return path_1.default.join(getProjectRoot(), '.env'); }
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@openfactu/cli",
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"description": "CLI para gestionar OpenFactu: migraciones, tenants, plugins y setup",
|
|
5
|
+
"main": "./dist/src/index.js",
|
|
6
|
+
"types": "./dist/src/index.d.ts",
|
|
7
|
+
"bin": {
|
|
8
|
+
"openfactu": "./dist/bin/openfactu.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"README.md"
|
|
13
|
+
],
|
|
14
|
+
"publishConfig": {
|
|
15
|
+
"access": "public"
|
|
16
|
+
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "tsc",
|
|
19
|
+
"dev": "ts-node bin/openfactu.ts"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"commander": "^12.0.0",
|
|
23
|
+
"chalk": "^4.1.2",
|
|
24
|
+
"ora": "^5.4.1",
|
|
25
|
+
"inquirer": "^8.2.6",
|
|
26
|
+
"dotenv": "^16.0.0",
|
|
27
|
+
"cli-table3": "^0.6.5",
|
|
28
|
+
"drizzle-orm": "^0.45.2",
|
|
29
|
+
"pg": "^8.13.0",
|
|
30
|
+
"bcrypt": "^5.1.1"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/inquirer": "^8.2.0",
|
|
34
|
+
"@types/pg": "^8.11.0",
|
|
35
|
+
"@types/bcrypt": "^5.0.0",
|
|
36
|
+
"typescript": "^5.3.3",
|
|
37
|
+
"ts-node": "^10.9.0"
|
|
38
|
+
}
|
|
39
|
+
}
|