@openfactu/cli 0.0.4 → 0.0.5
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/src/commands/deploy.js +1 -3
- package/dist/src/commands/plugin.js +266 -12
- package/package.json +11 -10
|
@@ -189,9 +189,7 @@ function registerDeployCommand(program) {
|
|
|
189
189
|
// 5. Generar docker-compose.prod.yml con bind a 0.0.0.0
|
|
190
190
|
const prodComposePath = path_1.default.join(root, 'docker-compose.prod.yml');
|
|
191
191
|
const prodSpinner = (0, ora_1.default)('Generando docker-compose.prod.yml...').start();
|
|
192
|
-
let composeContent = `
|
|
193
|
-
|
|
194
|
-
services:
|
|
192
|
+
let composeContent = `services:
|
|
195
193
|
web:
|
|
196
194
|
build:
|
|
197
195
|
context: .
|
|
@@ -7,15 +7,37 @@ exports.registerPluginCommand = registerPluginCommand;
|
|
|
7
7
|
const chalk_1 = __importDefault(require("chalk"));
|
|
8
8
|
const ora_1 = __importDefault(require("ora"));
|
|
9
9
|
const cli_table3_1 = __importDefault(require("cli-table3"));
|
|
10
|
+
const inquirer_1 = __importDefault(require("inquirer"));
|
|
11
|
+
const child_process_1 = require("child_process");
|
|
12
|
+
const https_1 = __importDefault(require("https"));
|
|
10
13
|
const fs_1 = __importDefault(require("fs"));
|
|
11
14
|
const path_1 = __importDefault(require("path"));
|
|
12
15
|
const db_1 = require("../utils/db");
|
|
13
16
|
const logger_1 = require("../utils/logger");
|
|
14
17
|
const paths_1 = require("../utils/paths");
|
|
18
|
+
// Registrar el plugin de autocomplete
|
|
19
|
+
const AutocompletePrompt = require('inquirer-autocomplete-prompt');
|
|
20
|
+
inquirer_1.default.registerPrompt('autocomplete', AutocompletePrompt);
|
|
21
|
+
function fetchJSON(url) {
|
|
22
|
+
return new Promise((resolve, reject) => {
|
|
23
|
+
https_1.default.get(url, { headers: { 'User-Agent': 'openfactu-cli' } }, (res) => {
|
|
24
|
+
let data = '';
|
|
25
|
+
res.on('data', (chunk) => (data += chunk));
|
|
26
|
+
res.on('end', () => {
|
|
27
|
+
try {
|
|
28
|
+
resolve(JSON.parse(data));
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
reject(new Error('Respuesta no valida'));
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
}).on('error', reject);
|
|
35
|
+
});
|
|
36
|
+
}
|
|
15
37
|
function registerPluginCommand(program) {
|
|
16
38
|
const plugin = program
|
|
17
39
|
.command('plugin')
|
|
18
|
-
.description('
|
|
40
|
+
.description('Gestion de plugins');
|
|
19
41
|
// ── openfactu plugin list ──
|
|
20
42
|
plugin
|
|
21
43
|
.command('list')
|
|
@@ -23,7 +45,6 @@ function registerPluginCommand(program) {
|
|
|
23
45
|
.action(async () => {
|
|
24
46
|
const spinner = (0, ora_1.default)('Leyendo plugins...').start();
|
|
25
47
|
try {
|
|
26
|
-
// Leer plugins del filesystem
|
|
27
48
|
const installed = [];
|
|
28
49
|
if (fs_1.default.existsSync((0, paths_1.getPluginsDir)())) {
|
|
29
50
|
const dirs = fs_1.default.readdirSync((0, paths_1.getPluginsDir)()).filter((d) => fs_1.default.statSync(path_1.default.join((0, paths_1.getPluginsDir)(), d)).isDirectory());
|
|
@@ -33,15 +54,12 @@ function registerPluginCommand(program) {
|
|
|
33
54
|
spinner.warn('No hay plugins instalados');
|
|
34
55
|
return;
|
|
35
56
|
}
|
|
36
|
-
// Leer estado de activación por tenant
|
|
37
57
|
const publicDb = (0, db_1.getPublicDb)();
|
|
38
58
|
let tenantPlugins = [];
|
|
39
59
|
try {
|
|
40
60
|
tenantPlugins = await publicDb.select().from((0, db_1.schema)().tenantPlugins);
|
|
41
61
|
}
|
|
42
|
-
catch {
|
|
43
|
-
// Tabla puede no existir aún
|
|
44
|
-
}
|
|
62
|
+
catch { }
|
|
45
63
|
const tenants = await (0, db_1.getAllTenants)();
|
|
46
64
|
spinner.succeed(`${installed.length} plugin(s) instalado(s)`);
|
|
47
65
|
logger_1.log.blank();
|
|
@@ -69,15 +87,12 @@ function registerPluginCommand(program) {
|
|
|
69
87
|
];
|
|
70
88
|
for (const tenant of tenants) {
|
|
71
89
|
const tp = tenantPlugins.find((r) => r.tenantId === tenant.id && r.pluginId === pluginId);
|
|
72
|
-
if (tp?.isActive)
|
|
90
|
+
if (tp?.isActive)
|
|
73
91
|
row.push(chalk_1.default.green('Activo'));
|
|
74
|
-
|
|
75
|
-
else if (tp) {
|
|
92
|
+
else if (tp)
|
|
76
93
|
row.push(chalk_1.default.dim('Inactivo'));
|
|
77
|
-
|
|
78
|
-
else {
|
|
94
|
+
else
|
|
79
95
|
row.push(chalk_1.default.dim('-'));
|
|
80
|
-
}
|
|
81
96
|
}
|
|
82
97
|
table.push(row);
|
|
83
98
|
}
|
|
@@ -91,4 +106,243 @@ function registerPluginCommand(program) {
|
|
|
91
106
|
await (0, db_1.disconnect)();
|
|
92
107
|
}
|
|
93
108
|
});
|
|
109
|
+
// ── openfactu plugin search ──
|
|
110
|
+
plugin
|
|
111
|
+
.command('search [query]')
|
|
112
|
+
.description('Busca plugins en el marketplace (interactivo)')
|
|
113
|
+
.action(async (query) => {
|
|
114
|
+
const spinner = (0, ora_1.default)('Cargando marketplace...').start();
|
|
115
|
+
let repos = [];
|
|
116
|
+
try {
|
|
117
|
+
const data = await fetchJSON('https://api.github.com/search/repositories?q=topic:openfactu-plugin&sort=stars&order=desc&per_page=50');
|
|
118
|
+
repos = data.items || [];
|
|
119
|
+
}
|
|
120
|
+
catch (err) {
|
|
121
|
+
spinner.fail('No se pudo conectar al marketplace: ' + err.message);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
if (repos.length === 0) {
|
|
125
|
+
spinner.warn('No hay plugins en el marketplace');
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
const installedDirs = fs_1.default.existsSync((0, paths_1.getPluginsDir)())
|
|
129
|
+
? fs_1.default.readdirSync((0, paths_1.getPluginsDir)()).filter((d) => fs_1.default.statSync(path_1.default.join((0, paths_1.getPluginsDir)(), d)).isDirectory())
|
|
130
|
+
: [];
|
|
131
|
+
spinner.succeed(`${repos.length} plugin(s) en el marketplace`);
|
|
132
|
+
logger_1.log.blank();
|
|
133
|
+
const { selected } = await inquirer_1.default.prompt([
|
|
134
|
+
{
|
|
135
|
+
type: 'autocomplete',
|
|
136
|
+
name: 'selected',
|
|
137
|
+
message: 'Buscar plugin:',
|
|
138
|
+
source: (_answers, input) => {
|
|
139
|
+
const q = (input || '').toLowerCase();
|
|
140
|
+
return repos
|
|
141
|
+
.filter((r) => !q || r.name.toLowerCase().includes(q) || (r.description || '').toLowerCase().includes(q))
|
|
142
|
+
.map((r) => {
|
|
143
|
+
const installed = installedDirs.includes(r.name);
|
|
144
|
+
const status = installed ? chalk_1.default.green(' [instalado]') : '';
|
|
145
|
+
const stars = chalk_1.default.yellow(`★${r.stargazers_count}`);
|
|
146
|
+
return {
|
|
147
|
+
name: `${chalk_1.default.bold(r.name)} ${chalk_1.default.dim('por ' + r.owner.login)} ${stars}${status}\n ${chalk_1.default.dim(r.description || 'Sin descripcion')}`,
|
|
148
|
+
value: r,
|
|
149
|
+
short: r.name,
|
|
150
|
+
};
|
|
151
|
+
});
|
|
152
|
+
},
|
|
153
|
+
pageSize: 10,
|
|
154
|
+
},
|
|
155
|
+
]);
|
|
156
|
+
const r = selected;
|
|
157
|
+
const isInstalled = installedDirs.includes(r.name);
|
|
158
|
+
const topics = (r.topics || []).filter((t) => t !== 'openfactu-plugin');
|
|
159
|
+
logger_1.log.blank();
|
|
160
|
+
console.log(chalk_1.default.bold.white(` ${r.name}`));
|
|
161
|
+
console.log(chalk_1.default.dim(` por ${r.owner.login} · ★ ${r.stargazers_count} · ${r.language || 'TypeScript'}`));
|
|
162
|
+
if (r.description)
|
|
163
|
+
console.log(` ${r.description}`);
|
|
164
|
+
if (topics.length > 0)
|
|
165
|
+
console.log(chalk_1.default.dim(` Tags: ${topics.join(', ')}`));
|
|
166
|
+
console.log(chalk_1.default.dim(` ${r.html_url}`));
|
|
167
|
+
logger_1.log.blank();
|
|
168
|
+
if (isInstalled) {
|
|
169
|
+
logger_1.log.success('Este plugin ya esta instalado');
|
|
170
|
+
const { action } = await inquirer_1.default.prompt([
|
|
171
|
+
{
|
|
172
|
+
type: 'list',
|
|
173
|
+
name: 'action',
|
|
174
|
+
message: 'Que quieres hacer?',
|
|
175
|
+
choices: [
|
|
176
|
+
{ name: 'Actualizar', value: 'update' },
|
|
177
|
+
{ name: 'Eliminar', value: 'remove' },
|
|
178
|
+
{ name: 'Nada', value: 'none' },
|
|
179
|
+
],
|
|
180
|
+
},
|
|
181
|
+
]);
|
|
182
|
+
if (action === 'update') {
|
|
183
|
+
const upSpinner = (0, ora_1.default)('Actualizando...').start();
|
|
184
|
+
try {
|
|
185
|
+
(0, child_process_1.execSync)('git pull --ff-only', { cwd: path_1.default.join((0, paths_1.getPluginsDir)(), r.name), stdio: 'pipe' });
|
|
186
|
+
upSpinner.succeed('Plugin actualizado');
|
|
187
|
+
}
|
|
188
|
+
catch {
|
|
189
|
+
upSpinner.warn('No se pudo actualizar');
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
else if (action === 'remove') {
|
|
193
|
+
fs_1.default.rmSync(path_1.default.join((0, paths_1.getPluginsDir)(), r.name), { recursive: true, force: true });
|
|
194
|
+
logger_1.log.success('Plugin eliminado');
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
const { install } = await inquirer_1.default.prompt([
|
|
199
|
+
{ type: 'confirm', name: 'install', message: 'Instalar este plugin?', default: true },
|
|
200
|
+
]);
|
|
201
|
+
if (install) {
|
|
202
|
+
const pluginsDir = (0, paths_1.getPluginsDir)();
|
|
203
|
+
if (!fs_1.default.existsSync(pluginsDir))
|
|
204
|
+
fs_1.default.mkdirSync(pluginsDir, { recursive: true });
|
|
205
|
+
const cloneSpinner = (0, ora_1.default)('Descargando...').start();
|
|
206
|
+
try {
|
|
207
|
+
(0, child_process_1.execSync)(`git clone ${r.clone_url} "${path_1.default.join(pluginsDir, r.name)}"`, { stdio: 'pipe', timeout: 60000 });
|
|
208
|
+
cloneSpinner.succeed(`Plugin "${r.name}" instalado`);
|
|
209
|
+
logger_1.log.dim(' Reinicia el servidor para cargarlo.');
|
|
210
|
+
}
|
|
211
|
+
catch (err) {
|
|
212
|
+
cloneSpinner.fail('Error: ' + err.message);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
// ── openfactu plugin install ──
|
|
218
|
+
plugin
|
|
219
|
+
.command('install <name>')
|
|
220
|
+
.description('Instala un plugin desde el marketplace')
|
|
221
|
+
.option('--repo <url>', 'URL del repositorio (si no es del marketplace)')
|
|
222
|
+
.action(async (name, opts) => {
|
|
223
|
+
const pluginsDir = (0, paths_1.getPluginsDir)();
|
|
224
|
+
const targetDir = path_1.default.join(pluginsDir, name);
|
|
225
|
+
// Verificar si ya esta instalado
|
|
226
|
+
if (fs_1.default.existsSync(targetDir)) {
|
|
227
|
+
logger_1.log.warn(`El plugin "${name}" ya esta instalado en ${targetDir}`);
|
|
228
|
+
logger_1.log.dim(' Para actualizar: openfactu plugin update ' + name);
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
let repoUrl = opts.repo;
|
|
232
|
+
if (!repoUrl) {
|
|
233
|
+
// Buscar en el marketplace
|
|
234
|
+
const spinner = (0, ora_1.default)(`Buscando "${name}" en el marketplace...`).start();
|
|
235
|
+
try {
|
|
236
|
+
const data = await fetchJSON('https://api.github.com/search/repositories?q=topic:openfactu-plugin+' + encodeURIComponent(name) + '&sort=stars&order=desc');
|
|
237
|
+
const match = (data.items || []).find((r) => r.name.toLowerCase() === name.toLowerCase());
|
|
238
|
+
if (!match) {
|
|
239
|
+
// Buscar sin filtro exacto
|
|
240
|
+
const fuzzy = (data.items || []).find((r) => r.name.toLowerCase().includes(name.toLowerCase()));
|
|
241
|
+
if (fuzzy) {
|
|
242
|
+
repoUrl = fuzzy.clone_url;
|
|
243
|
+
spinner.succeed(`Encontrado: ${fuzzy.full_name}`);
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
spinner.fail(`Plugin "${name}" no encontrado en el marketplace`);
|
|
247
|
+
logger_1.log.dim(' Usa --repo <url> para instalar desde un repositorio especifico');
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
repoUrl = match.clone_url;
|
|
253
|
+
spinner.succeed(`Encontrado: ${match.full_name}`);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
catch (err) {
|
|
257
|
+
spinner.fail('Error buscando: ' + err.message);
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
// Crear directorio de plugins si no existe
|
|
262
|
+
if (!fs_1.default.existsSync(pluginsDir)) {
|
|
263
|
+
fs_1.default.mkdirSync(pluginsDir, { recursive: true });
|
|
264
|
+
}
|
|
265
|
+
// Clonar
|
|
266
|
+
const cloneSpinner = (0, ora_1.default)('Descargando plugin...').start();
|
|
267
|
+
try {
|
|
268
|
+
(0, child_process_1.execSync)(`git clone ${repoUrl} "${targetDir}"`, { stdio: 'pipe', timeout: 60000 });
|
|
269
|
+
cloneSpinner.succeed('Plugin descargado');
|
|
270
|
+
}
|
|
271
|
+
catch (err) {
|
|
272
|
+
cloneSpinner.fail('Error al descargar: ' + err.message);
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
// Verificar estructura
|
|
276
|
+
const hasIndex = fs_1.default.existsSync(path_1.default.join(targetDir, 'index.ts')) || fs_1.default.existsSync(path_1.default.join(targetDir, 'index.js'));
|
|
277
|
+
const hasManifest = fs_1.default.existsSync(path_1.default.join(targetDir, 'manifest.json'));
|
|
278
|
+
logger_1.log.blank();
|
|
279
|
+
logger_1.log.success(`Plugin "${name}" instalado en ${targetDir}`);
|
|
280
|
+
logger_1.log.info(`Punto de entrada: ${hasIndex ? chalk_1.default.green('Si') : chalk_1.default.yellow('No encontrado')}`);
|
|
281
|
+
logger_1.log.info(`Manifest: ${hasManifest ? chalk_1.default.green('Si') : chalk_1.default.dim('No')}`);
|
|
282
|
+
logger_1.log.blank();
|
|
283
|
+
logger_1.log.dim(' Reinicia el servidor para cargar el plugin.');
|
|
284
|
+
logger_1.log.dim(' Activa el plugin por empresa desde la UI o API.');
|
|
285
|
+
});
|
|
286
|
+
// ── openfactu plugin update ──
|
|
287
|
+
plugin
|
|
288
|
+
.command('update [name]')
|
|
289
|
+
.description('Actualiza un plugin o todos')
|
|
290
|
+
.action(async (name) => {
|
|
291
|
+
const pluginsDir = (0, paths_1.getPluginsDir)();
|
|
292
|
+
if (!fs_1.default.existsSync(pluginsDir)) {
|
|
293
|
+
logger_1.log.warn('No hay plugins instalados');
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
const dirs = name
|
|
297
|
+
? [name]
|
|
298
|
+
: fs_1.default.readdirSync(pluginsDir).filter((d) => fs_1.default.statSync(path_1.default.join(pluginsDir, d)).isDirectory());
|
|
299
|
+
let updated = 0;
|
|
300
|
+
for (const dir of dirs) {
|
|
301
|
+
const pluginPath = path_1.default.join(pluginsDir, dir);
|
|
302
|
+
const gitDir = path_1.default.join(pluginPath, '.git');
|
|
303
|
+
if (!fs_1.default.existsSync(gitDir)) {
|
|
304
|
+
logger_1.log.dim(` ${dir} — no es un repositorio git, omitiendo`);
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
const spinner = (0, ora_1.default)(`Actualizando ${dir}...`).start();
|
|
308
|
+
try {
|
|
309
|
+
(0, child_process_1.execSync)('git pull --ff-only', { cwd: pluginPath, stdio: 'pipe', timeout: 30000 });
|
|
310
|
+
const status = (0, child_process_1.execSync)('git log --oneline -1', { cwd: pluginPath }).toString().trim();
|
|
311
|
+
spinner.succeed(`${dir} — ${status}`);
|
|
312
|
+
updated++;
|
|
313
|
+
}
|
|
314
|
+
catch (err) {
|
|
315
|
+
spinner.warn(`${dir} — no se pudo actualizar`);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
logger_1.log.blank();
|
|
319
|
+
if (updated > 0) {
|
|
320
|
+
logger_1.log.success(`${updated} plugin(s) actualizado(s)`);
|
|
321
|
+
logger_1.log.dim(' Reinicia el servidor para aplicar los cambios.');
|
|
322
|
+
}
|
|
323
|
+
else {
|
|
324
|
+
logger_1.log.info('No hay actualizaciones');
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
// ── openfactu plugin remove ──
|
|
328
|
+
plugin
|
|
329
|
+
.command('remove <name>')
|
|
330
|
+
.description('Elimina un plugin instalado')
|
|
331
|
+
.action(async (name) => {
|
|
332
|
+
const targetDir = path_1.default.join((0, paths_1.getPluginsDir)(), name);
|
|
333
|
+
if (!fs_1.default.existsSync(targetDir)) {
|
|
334
|
+
logger_1.log.error(`Plugin "${name}" no encontrado`);
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
const spinner = (0, ora_1.default)(`Eliminando ${name}...`).start();
|
|
338
|
+
try {
|
|
339
|
+
fs_1.default.rmSync(targetDir, { recursive: true, force: true });
|
|
340
|
+
spinner.succeed(`Plugin "${name}" eliminado`);
|
|
341
|
+
logger_1.log.dim(' Reinicia el servidor para aplicar los cambios.');
|
|
342
|
+
logger_1.log.dim(' Los datos del plugin (campos, tablas) se mantienen en la BD.');
|
|
343
|
+
}
|
|
344
|
+
catch (err) {
|
|
345
|
+
spinner.fail('Error: ' + err.message);
|
|
346
|
+
}
|
|
347
|
+
});
|
|
94
348
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openfactu/cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
4
4
|
"description": "CLI para gestionar OpenFactu: migraciones, tenants, plugins y setup",
|
|
5
5
|
"main": "./dist/src/index.js",
|
|
6
6
|
"types": "./dist/src/index.d.ts",
|
|
@@ -19,21 +19,22 @@
|
|
|
19
19
|
"dev": "ts-node bin/openfactu.ts"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"
|
|
22
|
+
"bcrypt": "^5.1.1",
|
|
23
23
|
"chalk": "^4.1.2",
|
|
24
|
-
"ora": "^5.4.1",
|
|
25
|
-
"inquirer": "^8.2.6",
|
|
26
|
-
"dotenv": "^16.0.0",
|
|
27
24
|
"cli-table3": "^0.6.5",
|
|
25
|
+
"commander": "^12.0.0",
|
|
26
|
+
"dotenv": "^16.0.0",
|
|
28
27
|
"drizzle-orm": "^0.45.2",
|
|
29
|
-
"
|
|
30
|
-
"
|
|
28
|
+
"inquirer": "^8.2.6",
|
|
29
|
+
"inquirer-autocomplete-prompt": "^2.0.1",
|
|
30
|
+
"ora": "^5.4.1",
|
|
31
|
+
"pg": "^8.13.0"
|
|
31
32
|
},
|
|
32
33
|
"devDependencies": {
|
|
34
|
+
"@types/bcrypt": "^5.0.0",
|
|
33
35
|
"@types/inquirer": "^8.2.0",
|
|
34
36
|
"@types/pg": "^8.11.0",
|
|
35
|
-
"
|
|
36
|
-
"typescript": "^5.3.3"
|
|
37
|
-
"ts-node": "^10.9.0"
|
|
37
|
+
"ts-node": "^10.9.0",
|
|
38
|
+
"typescript": "^5.3.3"
|
|
38
39
|
}
|
|
39
40
|
}
|