@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.
@@ -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,2 @@
1
+ import { Command } from 'commander';
2
+ export declare function registerVersionCommand(program: Command): void;
@@ -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,2 @@
1
+ import { Command } from 'commander';
2
+ export declare function createCLI(): Command;
@@ -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,8 @@
1
+ /**
2
+ * Carga la configuración desde .env del proyecto OpenFactu.
3
+ */
4
+ export declare function loadConfig(): {
5
+ databaseUrl: string;
6
+ jwtSecret: string;
7
+ serverPort: string;
8
+ };
@@ -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,9 @@
1
+ export declare const log: {
2
+ info: (msg: string) => void;
3
+ success: (msg: string) => void;
4
+ warn: (msg: string) => void;
5
+ error: (msg: string) => void;
6
+ dim: (msg: string) => void;
7
+ title: (msg: string) => void;
8
+ blank: () => void;
9
+ };
@@ -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
+ }