utn-cli 2.0.49 → 2.0.51
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/commands/backend.js +180 -71
- package/commands/createComponent.js +128 -0
- package/commands/db.js +37 -0
- package/commands/frontend.js +64 -10
- package/commands/update.js +76 -0
- package/index.js +22 -2
- package/package.json +1 -1
- package/templates/frontend/src/app/Paginas/gestion-tabla-XYZ/gestion-tabla-XYZ.component.html +1 -1
package/commands/backend.js
CHANGED
|
@@ -149,7 +149,7 @@ export async function initBackend() {
|
|
|
149
149
|
closeReadLine();
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
-
export async function updateBackend() {
|
|
152
|
+
export async function updateBackend(opciones = { cerrarAlFinalizar: true }) {
|
|
153
153
|
console.log('Actualizando el proyecto de backend...');
|
|
154
154
|
const archivosAExcluir = ['InformacionDelModulo.json', 'rutas.js', 'Monitoreo.js', 'API.js', 'NOMBRE_DEL_CANONICO_DEL_PROYECTO.rest'];
|
|
155
155
|
const directoriodePlantillas = path.join(__dirname, '../templates/backend');
|
|
@@ -166,11 +166,16 @@ export async function updateBackend() {
|
|
|
166
166
|
fs.renameSync('gitignore', '.gitignore');
|
|
167
167
|
await inicializarProyectoBackend();
|
|
168
168
|
console.log('Proyecto de backend actualizado exitosamente.');
|
|
169
|
-
|
|
169
|
+
if (opciones.cerrarAlFinalizar) {
|
|
170
|
+
closeReadLine();
|
|
171
|
+
}
|
|
170
172
|
}
|
|
171
173
|
|
|
172
|
-
export async function addServiceBackend() {
|
|
173
|
-
let Servicio =
|
|
174
|
+
export async function addServiceBackend(nombreServicio = null, opciones = { cerrarAlFinalizar: true }, tituloServicio = null, tableInfo = null) {
|
|
175
|
+
let Servicio = nombreServicio;
|
|
176
|
+
if (!Servicio) {
|
|
177
|
+
Servicio = await hacerPreguntaTrim('¿Cuál es el nombre del servicio?: ');
|
|
178
|
+
}
|
|
174
179
|
Servicio = Servicio.replace(/\s/g, '');
|
|
175
180
|
const rutaServicioDir = path.join(process.cwd(), 'servicios');
|
|
176
181
|
const rutaRutaDir = path.join(process.cwd(), 'rutas');
|
|
@@ -182,7 +187,7 @@ export async function addServiceBackend() {
|
|
|
182
187
|
fs.copyFileSync(templateServicioPath, newServicioPath);
|
|
183
188
|
} else {
|
|
184
189
|
console.error(`Error: Template Servicio1.js not found in ${rutaServicioDir}`);
|
|
185
|
-
closeReadLine();
|
|
190
|
+
if (opciones.cerrarAlFinalizar) closeReadLine();
|
|
186
191
|
process.exit(1);
|
|
187
192
|
}
|
|
188
193
|
|
|
@@ -192,7 +197,7 @@ export async function addServiceBackend() {
|
|
|
192
197
|
fs.copyFileSync(templateRutaPath, newRutaPath);
|
|
193
198
|
} else {
|
|
194
199
|
console.error(`Error: Template Servicio1.js not found in ${rutaRutaDir}`);
|
|
195
|
-
closeReadLine();
|
|
200
|
+
if (opciones.cerrarAlFinalizar) closeReadLine();
|
|
196
201
|
process.exit(1);
|
|
197
202
|
}
|
|
198
203
|
|
|
@@ -201,86 +206,190 @@ export async function addServiceBackend() {
|
|
|
201
206
|
if (fs.existsSync(rutaRutasJs)) {
|
|
202
207
|
let contenidoRutasJs = fs.readFileSync(rutaRutasJs, 'utf-8');
|
|
203
208
|
const nuevaRutaRequire = `const rutasDel${Servicio} = require('./${Servicio}.js');`;
|
|
204
|
-
|
|
205
|
-
|
|
209
|
+
|
|
210
|
+
// Agregar require si no existe
|
|
211
|
+
if (!contenidoRutasJs.includes(nuevaRutaRequire)) {
|
|
212
|
+
contenidoRutasJs = contenidoRutasJs.replace(/function asignarRutasAExpress\(app\) \{/, `${nuevaRutaRequire}\n\nfunction asignarRutasAExpress(app) {`);
|
|
213
|
+
}
|
|
206
214
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
215
|
+
// Agregar app.use al final de la función
|
|
216
|
+
const lineaAppUse = ` app.use('/${Servicio}', rutasDel${Servicio});`;
|
|
217
|
+
if (!contenidoRutasJs.includes(lineaAppUse)) {
|
|
218
|
+
// Buscamos el final de la función asignarRutasAExpress
|
|
219
|
+
contenidoRutasJs = contenidoRutasJs.replace(/(function asignarRutasAExpress\(app\) \{[\s\S]*?)(\n\})/, `$1\n${lineaAppUse}$2`);
|
|
220
|
+
}
|
|
221
|
+
|
|
210
222
|
fs.writeFileSync(rutaRutasJs, contenidoRutasJs);
|
|
211
223
|
} else {
|
|
212
224
|
console.error(`Error: rutas.js not found in ${rutaRutaDir}`);
|
|
213
|
-
closeReadLine();
|
|
225
|
+
if (opciones.cerrarAlFinalizar) closeReadLine();
|
|
214
226
|
process.exit(1);
|
|
215
227
|
}
|
|
216
228
|
|
|
229
|
+
// Si tenemos información de la tabla, generamos el CRUD
|
|
230
|
+
if (tableInfo) {
|
|
231
|
+
generarCRUDBackend(newServicioPath, newRutaPath, Servicio, tableInfo);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Reemplazar el título amigable si se proporciona
|
|
235
|
+
if (tituloServicio) {
|
|
236
|
+
reemplazarContenidoEnArchivo(newServicioPath, "const NombreDelServicio = 'Servicio1';", `const NombreDelServicio = '${tituloServicio}';`);
|
|
237
|
+
}
|
|
238
|
+
|
|
217
239
|
reemplazarContenidoEnArchivo(newServicioPath, 'Servicio1', Servicio);
|
|
218
240
|
reemplazarContenidoEnArchivo(newRutaPath, 'Servicio1', Servicio);
|
|
219
241
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
const
|
|
228
|
-
const
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
const
|
|
242
|
+
console.log(`Servicio ${Servicio} agregado exitosamente.`);
|
|
243
|
+
if (opciones.cerrarAlFinalizar) {
|
|
244
|
+
closeReadLine();
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function generarCRUDBackend(servicioPath, rutaPath, nombreServicio, tableInfo) {
|
|
249
|
+
const { dbName, tableName, columns, primaryKey } = tableInfo;
|
|
250
|
+
const dbNameSinDB = dbName.endsWith('-db') ? dbName.slice(0, -3) : dbName;
|
|
251
|
+
|
|
252
|
+
// 1. Generar funciones en el Servicio
|
|
253
|
+
let contenidoServicio = fs.readFileSync(servicioPath, 'utf-8');
|
|
254
|
+
|
|
255
|
+
const colNames = columns.map(c => `\`${c.name}\``).join(', ');
|
|
256
|
+
const colPlaceholders = columns.map(() => '?').join(', ');
|
|
257
|
+
const colUpdates = columns.filter(c => c.name !== primaryKey).map(c => `\`${c.name}\` = ?`).join(', ');
|
|
258
|
+
const colUpdateParams = columns.filter(c => c.name !== primaryKey).map(c => `Datos.${c.name}`).join(', ');
|
|
259
|
+
const colInsertParams = columns.map(c => `Datos.${c.name}`).join(', ');
|
|
260
|
+
|
|
261
|
+
const crudFunciones = `
|
|
262
|
+
async listar() {
|
|
263
|
+
return await ejecutarConsulta("SELECT ${colNames} FROM \`${dbNameSinDB}\`.\`${tableName}\` COLLATE utf8mb4_spanish_ci");
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
async agregar(Datos) {
|
|
267
|
+
return ejecutarConsulta("INSERT INTO \`${dbNameSinDB}\`.\`${tableName}\` (${colNames}, \`LastUpdate\`, \`LastUser\`) VALUES (${colPlaceholders}, NOW(4), ?)"
|
|
268
|
+
, [${colInsertParams}, Datos.LastUser]);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
async borrar(Datos) {
|
|
272
|
+
return ejecutarConsulta("DELETE FROM \`${dbNameSinDB}\`.\`${tableName}\` WHERE \`${primaryKey}\` = ?"
|
|
273
|
+
, [Datos.${primaryKey}]);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
async actualizar(Datos) {
|
|
277
|
+
return ejecutarConsulta("UPDATE \`${dbNameSinDB}\`.\`${tableName}\` SET ${colUpdates}, \`LastUpdate\` = NOW(4), \`LastUser\` = ? WHERE \`${primaryKey}\` = ?"
|
|
278
|
+
, [${colUpdateParams}, Datos.LastUser, Datos.${primaryKey}]);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
async detalle(${primaryKey}) {
|
|
282
|
+
return await ejecutarConsulta("SELECT ${colNames} FROM \`${dbNameSinDB}\`.\`${tableName}\` WHERE \`${primaryKey}\` = ?"
|
|
283
|
+
, [${primaryKey}]);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
async reporte() {
|
|
287
|
+
return await Miscelaneas.convertirACSV(await this.listar());
|
|
288
|
+
}
|
|
289
|
+
`;
|
|
290
|
+
|
|
291
|
+
// Insertar antes del último cierre de llave
|
|
292
|
+
contenidoServicio = contenidoServicio.replace(/\n\}\s*\nmodule\.exports/, crudFunciones + '\n}\n\nmodule.exports');
|
|
293
|
+
fs.writeFileSync(servicioPath, contenidoServicio);
|
|
294
|
+
|
|
295
|
+
// 2. Generar rutas
|
|
296
|
+
let contenidoRutas = fs.readFileSync(rutaPath, 'utf-8');
|
|
297
|
+
|
|
298
|
+
const crudRutas = `
|
|
299
|
+
Router.get('/listar', async (solicitud, respuesta, next) => {
|
|
234
300
|
try {
|
|
235
|
-
|
|
301
|
+
if (await Miscelaneo.validarTokenV2(solicitud.headers.authorization) && await Miscelaneo.validarAccesoDelOrigen(solicitud)) {
|
|
302
|
+
try {
|
|
303
|
+
return respuesta.json({ body: await ${nombreServicio}.listar(), error: undefined });
|
|
304
|
+
} catch (error) {
|
|
305
|
+
const MensajeDeError = 'No fue posible listar los registros';
|
|
306
|
+
console.error(new ManejadorDeErrores(MensajeDeError, ManejadorDeErrores.obtenerNumeroDeLinea(), true, \`Dirección IP: \${solicitud.ip}\`));
|
|
307
|
+
return respuesta.status(500).json({ body: undefined, error: MensajeDeError });
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
return respuesta.status(401).json({ body: undefined, error: ManejadorDeErrores.mensajeDeError401() });
|
|
236
311
|
} catch (error) {
|
|
237
|
-
|
|
312
|
+
next(error);
|
|
238
313
|
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
Router.post('/agregar', async (solicitud, respuesta, next) => {
|
|
317
|
+
try {
|
|
318
|
+
if (await Miscelaneo.validarTokenV2(solicitud.headers.authorization) && await Miscelaneo.validarAccesoDelOrigen(solicitud)) {
|
|
319
|
+
try {
|
|
320
|
+
const Datos = solicitud.body;
|
|
321
|
+
Datos.LastUser = await Miscelaneo.generarLastUser(solicitud);
|
|
322
|
+
return respuesta.json({ body: await ${nombreServicio}.agregar(Datos), error: undefined });
|
|
323
|
+
} catch (error) {
|
|
324
|
+
const MensajeDeError = 'No fue posible agregar el registro';
|
|
325
|
+
console.error(new ManejadorDeErrores(MensajeDeError, ManejadorDeErrores.obtenerNumeroDeLinea(), true, \`Dirección IP: \${solicitud.ip}\`));
|
|
326
|
+
return respuesta.status(500).json({ body: undefined, error: MensajeDeError });
|
|
327
|
+
}
|
|
246
328
|
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
`gestion-tabla-${servicioEnMinuscula}.component`
|
|
253
|
-
);
|
|
254
|
-
const servicioCapitalizado = Servicio.charAt(0).toUpperCase() + Servicio.slice(1);
|
|
255
|
-
contenidoTs = contenidoTs.replace(
|
|
256
|
-
/export class GestionTablaComponent/,
|
|
257
|
-
`export class GestionTabla${servicioCapitalizado}Component`
|
|
258
|
-
);
|
|
259
|
-
fs.writeFileSync(rutaArchivoTs, contenidoTs, 'utf8');
|
|
260
|
-
const rutaAppRoutes = path.join(rutaDelFrontEnd, '../app.routes.ts');
|
|
261
|
-
const nombreComponente = `GestionTabla${servicioCapitalizado}Component`;
|
|
262
|
-
const rutaComponente = `./Paginas/gestion-tabla-${servicioEnMinuscula}/gestion-tabla-${servicioEnMinuscula}.component`;
|
|
263
|
-
const nuevaLineaImport = `import { ${nombreComponente} } from '${rutaComponente}';`;
|
|
264
|
-
const nuevaLineaRuta = `,
|
|
265
|
-
{ path: '${servicioEnMinuscula}', component: ${nombreComponente} }`;
|
|
266
|
-
let contenidoRoutes = fs.readFileSync(rutaAppRoutes, 'utf8');
|
|
267
|
-
contenidoRoutes = contenidoRoutes.replace(
|
|
268
|
-
/\s*export const routes: Routes = \[/,
|
|
269
|
-
`
|
|
270
|
-
${nuevaLineaImport}
|
|
271
|
-
|
|
272
|
-
export const routes: Routes = [`
|
|
273
|
-
);
|
|
274
|
-
contenidoRoutes = contenidoRoutes.replace(
|
|
275
|
-
/\s*];/,
|
|
276
|
-
`${nuevaLineaRuta}
|
|
277
|
-
];`
|
|
278
|
-
);
|
|
279
|
-
fs.writeFileSync(rutaAppRoutes, contenidoRoutes, 'utf8');
|
|
280
|
-
*/
|
|
329
|
+
return respuesta.status(401).json({ body: undefined, error: ManejadorDeErrores.mensajeDeError401() });
|
|
330
|
+
} catch (error) {
|
|
331
|
+
next(error);
|
|
332
|
+
}
|
|
333
|
+
});
|
|
281
334
|
|
|
282
|
-
|
|
283
|
-
|
|
335
|
+
Router.post('/actualizar', async (solicitud, respuesta, next) => {
|
|
336
|
+
try {
|
|
337
|
+
if (await Miscelaneo.validarTokenV2(solicitud.headers.authorization) && await Miscelaneo.validarAccesoDelOrigen(solicitud)) {
|
|
338
|
+
try {
|
|
339
|
+
const Datos = solicitud.body;
|
|
340
|
+
Datos.LastUser = await Miscelaneo.generarLastUser(solicitud);
|
|
341
|
+
return respuesta.json({ body: await ${nombreServicio}.actualizar(Datos), error: undefined });
|
|
342
|
+
} catch (error) {
|
|
343
|
+
const MensajeDeError = 'No fue posible actualizar el registro';
|
|
344
|
+
console.error(new ManejadorDeErrores(MensajeDeError, ManejadorDeErrores.obtenerNumeroDeLinea(), true, \`Dirección IP: \${solicitud.ip}\`));
|
|
345
|
+
return respuesta.status(500).json({ body: undefined, error: MensajeDeError });
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
return respuesta.status(401).json({ body: undefined, error: ManejadorDeErrores.mensajeDeError401() });
|
|
349
|
+
} catch (error) {
|
|
350
|
+
next(error);
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
Router.post('/borrar', async (solicitud, respuesta, next) => {
|
|
355
|
+
try {
|
|
356
|
+
if (await Miscelaneo.validarTokenV2(solicitud.headers.authorization) && await Miscelaneo.validarAccesoDelOrigen(solicitud)) {
|
|
357
|
+
try {
|
|
358
|
+
return respuesta.json({ body: await ${nombreServicio}.borrar(solicitud.body), error: undefined });
|
|
359
|
+
} catch (error) {
|
|
360
|
+
const MensajeDeError = 'No fue posible borrar el registro';
|
|
361
|
+
console.error(new ManejadorDeErrores(MensajeDeError, ManejadorDeErrores.obtenerNumeroDeLinea(), true, \`Dirección IP: \${solicitud.ip}\`));
|
|
362
|
+
return respuesta.status(500).json({ body: undefined, error: MensajeDeError });
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
return respuesta.status(401).json({ body: undefined, error: ManejadorDeErrores.mensajeDeError401() });
|
|
366
|
+
} catch (error) {
|
|
367
|
+
next(error);
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
Router.get('/reporte', async (solicitud, respuesta, next) => {
|
|
372
|
+
try {
|
|
373
|
+
if (await Miscelaneo.validarTokenV2(solicitud.headers.authorization) && await Miscelaneo.validarAccesoDelOrigen(solicitud)) {
|
|
374
|
+
try {
|
|
375
|
+
respuesta.header('Content-Type', 'text/csv; charset=utf-8');
|
|
376
|
+
respuesta.attachment('Datos.csv');
|
|
377
|
+
return respuesta.send(encodeURI(await ${nombreServicio}.reporte()));
|
|
378
|
+
} catch (error) {
|
|
379
|
+
const MensajeDeError = 'No fue posible generar el reporte';
|
|
380
|
+
console.error(new ManejadorDeErrores(MensajeDeError, ManejadorDeErrores.obtenerNumeroDeLinea(), true, \`Dirección IP: \${solicitud.ip}\`));
|
|
381
|
+
return respuesta.status(500).json({ body: undefined, error: MensajeDeError });
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
return respuesta.status(401).json({ body: undefined, error: ManejadorDeErrores.mensajeDeError401() });
|
|
385
|
+
} catch (error) {
|
|
386
|
+
next(error);
|
|
387
|
+
}
|
|
388
|
+
});
|
|
389
|
+
`;
|
|
390
|
+
|
|
391
|
+
contenidoRutas = contenidoRutas.replace(/module\.exports = Router;/, crudRutas + '\nmodule.exports = Router;');
|
|
392
|
+
fs.writeFileSync(rutaPath, contenidoRutas);
|
|
284
393
|
}
|
|
285
394
|
|
|
286
395
|
export function showBackendVersion() {
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { addServiceBackend } from './backend.js';
|
|
4
|
+
import { cloneFrontendComponent } from './frontend.js';
|
|
5
|
+
import { closeReadLine, hacerPreguntaTrim } from '../utils/index.js';
|
|
6
|
+
|
|
7
|
+
export async function createComponent() {
|
|
8
|
+
const currentDir = process.cwd();
|
|
9
|
+
|
|
10
|
+
const nombreServicio = await hacerPreguntaTrim('¿Cuál es el nombre del nuevo servicio/componente? (Nombre técnico, ej: Incapacidades): ');
|
|
11
|
+
if (!nombreServicio) {
|
|
12
|
+
console.error('El nombre es requerido.');
|
|
13
|
+
closeReadLine();
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const tituloCard = await hacerPreguntaTrim('Ingrese el título para la tarjeta del frontend (ej: Gestión de Incapacidades): ');
|
|
18
|
+
const descripcionCard = await hacerPreguntaTrim('Ingrese la descripción para la tarjeta del frontend: ');
|
|
19
|
+
|
|
20
|
+
const entries = fs.readdirSync(currentDir, { withFileTypes: true });
|
|
21
|
+
const directories = entries.filter(entry => entry.isDirectory());
|
|
22
|
+
|
|
23
|
+
// Buscar información de la tabla en la base de datos
|
|
24
|
+
let tableInfo = null;
|
|
25
|
+
const dbDir = directories.find(d => d.name.toLowerCase().includes('bd') || d.name.toLowerCase().includes('db'));
|
|
26
|
+
|
|
27
|
+
if (dbDir) {
|
|
28
|
+
const sqlPath = path.join(currentDir, dbDir.name, 'docker-scripts/1-crear estructura.sql');
|
|
29
|
+
if (fs.existsSync(sqlPath)) {
|
|
30
|
+
console.log(`Buscando tabla para el servicio "${tituloCard}" en ${sqlPath}...`);
|
|
31
|
+
tableInfo = findTableByComment(sqlPath, tituloCard);
|
|
32
|
+
if (tableInfo) {
|
|
33
|
+
console.log(`Tabla encontrada: ${tableInfo.tableName} en la base de datos ${tableInfo.dbName}`);
|
|
34
|
+
} else {
|
|
35
|
+
console.log('No se encontró una tabla con un comentario que coincida con el título del servicio.');
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let foundBackend = false;
|
|
41
|
+
let foundFrontend = false;
|
|
42
|
+
|
|
43
|
+
for (const dir of directories) {
|
|
44
|
+
const dirName = dir.name.toLowerCase();
|
|
45
|
+
const fullPath = path.join(currentDir, dir.name);
|
|
46
|
+
|
|
47
|
+
if (dirName.includes('back')) {
|
|
48
|
+
console.log(`\nAgregando servicio a Backend en: ${dir.name}`);
|
|
49
|
+
process.chdir(fullPath);
|
|
50
|
+
try {
|
|
51
|
+
await addServiceBackend(nombreServicio, { cerrarAlFinalizar: false }, tituloCard, tableInfo);
|
|
52
|
+
foundBackend = true;
|
|
53
|
+
} catch (error) {
|
|
54
|
+
console.error(`Error al agregar servicio en ${dir.name}:`, error);
|
|
55
|
+
}
|
|
56
|
+
process.chdir(currentDir);
|
|
57
|
+
} else if (dirName.includes('front')) {
|
|
58
|
+
console.log(`\nAgregando componente a Frontend en: ${dir.name}`);
|
|
59
|
+
process.chdir(fullPath);
|
|
60
|
+
try {
|
|
61
|
+
await cloneFrontendComponent(nombreServicio, tituloCard, descripcionCard, { cerrarAlFinalizar: false }, tableInfo);
|
|
62
|
+
foundFrontend = true;
|
|
63
|
+
} catch (error) {
|
|
64
|
+
console.error(`Error al clonar componente en ${dir.name}:`, error);
|
|
65
|
+
}
|
|
66
|
+
process.chdir(currentDir);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (!foundBackend && !foundFrontend) {
|
|
71
|
+
console.log('No se encontraron carpetas con "back" o "front" para procesar.');
|
|
72
|
+
} else {
|
|
73
|
+
console.log('\nProceso de creación de componente finalizado.');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
closeReadLine();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function findTableByComment(sqlPath, serviceTitle) {
|
|
80
|
+
const content = fs.readFileSync(sqlPath, 'utf-8');
|
|
81
|
+
|
|
82
|
+
// Regex para encontrar tablas y sus comentarios
|
|
83
|
+
// Buscamos CREATE OR REPLACE TABLE `db`.`table` ... COMMENT = '...serviceTitle...'
|
|
84
|
+
const tableRegex = /CREATE OR REPLACE TABLE `(.+?)`\.`(.+?)` \(([\s\S]+?)\) ENGINE = InnoDB.+?COMMENT = '.*?(?:servicio de |almacena los datos del servicio de |almacena los datos del servicio de: )?([\s\S]+?)';/gi;
|
|
85
|
+
|
|
86
|
+
let match;
|
|
87
|
+
while ((match = tableRegex.exec(content)) !== null) {
|
|
88
|
+
const dbName = match[1];
|
|
89
|
+
const tableName = match[2];
|
|
90
|
+
const tableBody = match[3];
|
|
91
|
+
const tableComment = match[4].trim();
|
|
92
|
+
|
|
93
|
+
// Si el comentario coincide con el título del servicio
|
|
94
|
+
if (tableComment.toLowerCase().includes(serviceTitle.toLowerCase()) || serviceTitle.toLowerCase().includes(tableComment.toLowerCase())) {
|
|
95
|
+
const columns = [];
|
|
96
|
+
let primaryKey = '';
|
|
97
|
+
|
|
98
|
+
// Extraer columnas
|
|
99
|
+
const colRegex = /`(.+?)` (.+?)(?: COMMENT '(.+?)')?,/g;
|
|
100
|
+
let colMatch;
|
|
101
|
+
while ((colMatch = colRegex.exec(tableBody)) !== null) {
|
|
102
|
+
const colName = colMatch[1];
|
|
103
|
+
if (colName !== 'LastUpdate' && colName !== 'LastUser') {
|
|
104
|
+
columns.push({
|
|
105
|
+
name: colName,
|
|
106
|
+
type: colMatch[2],
|
|
107
|
+
alias: colMatch[3] || colName
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Extraer Primary Key
|
|
113
|
+
const pkMatch = tableBody.match(/PRIMARY KEY \(`(.+?)`\)/);
|
|
114
|
+
if (pkMatch) {
|
|
115
|
+
primaryKey = pkMatch[1];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
dbName,
|
|
120
|
+
tableName,
|
|
121
|
+
columns,
|
|
122
|
+
primaryKey,
|
|
123
|
+
serviceTitle
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return null;
|
|
128
|
+
}
|
package/commands/db.js
CHANGED
|
@@ -43,6 +43,43 @@ export async function initDb() {
|
|
|
43
43
|
closeReadLine();
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
export async function addTableDb() {
|
|
47
|
+
const rutaEstructura = path.join(process.cwd(), 'docker-scripts/1-crear estructura.sql');
|
|
48
|
+
|
|
49
|
+
if (!fs.existsSync(rutaEstructura)) {
|
|
50
|
+
console.error(`No se encontró el archivo de estructura en: ${rutaEstructura}. Asegúrese de estar en la carpeta raíz del proyecto de base de datos.`);
|
|
51
|
+
closeReadLine();
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const nombreTabla = await hacerPregunta('¿Cuál es el nombre de la tabla?: ');
|
|
56
|
+
const nombreServicio = await hacerPregunta('¿Cuál es el nombre del servicio que almacenará los datos?: ');
|
|
57
|
+
|
|
58
|
+
const contenidoEstructura = fs.readFileSync(rutaEstructura, 'utf-8');
|
|
59
|
+
|
|
60
|
+
// Intentar extraer el nombre del repositorio de la base de datos
|
|
61
|
+
const matchDb = contenidoEstructura.match(/CREATE DATABASE `(.+?)`/);
|
|
62
|
+
let nombreDb = 'NOMBRE_DEL_REPOSITORIO_DE_BASE_DE_DATOS';
|
|
63
|
+
|
|
64
|
+
if (matchDb && matchDb[1]) {
|
|
65
|
+
nombreDb = matchDb[1];
|
|
66
|
+
} else {
|
|
67
|
+
console.warn('No se pudo detectar el nombre de la base de datos en el archivo de estructura. Se usará el marcador por defecto.');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const machote = `
|
|
71
|
+
CREATE OR REPLACE TABLE \`${nombreDb}\`.\`${nombreTabla}\` (
|
|
72
|
+
\`LastUpdate\` DATETIME(4) NOT NULL DEFAULT CURRENT_TIMESTAMP(4) ON UPDATE CURRENT_TIMESTAMP(4) COMMENT 'Fecha de la última actualización de la fila',
|
|
73
|
+
\`LastUser\` VARCHAR(1000) COLLATE utf8mb4_spanish_ci NOT NULL DEFAULT '-' COMMENT 'Último usuario que modificó la fila',
|
|
74
|
+
PRIMARY KEY (\`\`)
|
|
75
|
+
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_spanish_ci COMMENT = 'Almacena los datos del servicio de ${nombreServicio}';
|
|
76
|
+
`;
|
|
77
|
+
|
|
78
|
+
fs.appendFileSync(rutaEstructura, machote);
|
|
79
|
+
console.log(`Tabla \`${nombreTabla}\` agregada exitosamente a ${rutaEstructura}`);
|
|
80
|
+
closeReadLine();
|
|
81
|
+
}
|
|
82
|
+
|
|
46
83
|
export function showDbVersion() {
|
|
47
84
|
// Assuming the version for 'db' command comes from the main utn-cli package.json
|
|
48
85
|
mostrarVersion(path.join(__dirname, '../package.json'));
|
package/commands/frontend.js
CHANGED
|
@@ -40,7 +40,7 @@ export async function initFrontend() {
|
|
|
40
40
|
closeReadLine();
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
export async function updateFrontend() {
|
|
43
|
+
export async function updateFrontend(opciones = { cerrarAlFinalizar: true }) {
|
|
44
44
|
console.log('Actualizando el proyecto de frontend...');
|
|
45
45
|
const archivosAExcluir = ['app.routes.ts', 'contenedor-principal.component.css', 'contenedor-principal.component.html', 'contenedor-principal.component.ts', '.vscode', 'dist'];
|
|
46
46
|
const directoriodePlantillas = path.join(__dirname, '../templates/frontend');
|
|
@@ -57,19 +57,30 @@ export async function updateFrontend() {
|
|
|
57
57
|
fs.renameSync('gitignore', '.gitignore');
|
|
58
58
|
fs.renameSync('editorconfig', '.editorconfig');
|
|
59
59
|
console.log('Proyecto de frontend actualizado exitosamente.');
|
|
60
|
-
|
|
60
|
+
if (opciones.cerrarAlFinalizar) {
|
|
61
|
+
closeReadLine();
|
|
62
|
+
}
|
|
61
63
|
}
|
|
62
64
|
|
|
63
|
-
export async function cloneFrontendComponent() {
|
|
64
|
-
|
|
65
|
+
export async function cloneFrontendComponent(nombreComponente = null, tituloCard = null, descripcionCard = null, opciones = { cerrarAlFinalizar: true }, tableInfo = null) {
|
|
66
|
+
let nombre = nombreComponente;
|
|
67
|
+
if (!nombre) {
|
|
68
|
+
nombre = await hacerPreguntaTrim('Ingrese el nombre del nuevo componente (se reemplazará XYZ): ');
|
|
69
|
+
}
|
|
65
70
|
if (!nombre) {
|
|
66
71
|
console.error('El nombre es requerido.');
|
|
67
|
-
closeReadLine();
|
|
72
|
+
if (opciones.cerrarAlFinalizar) closeReadLine();
|
|
68
73
|
process.exit(1);
|
|
69
74
|
}
|
|
70
75
|
|
|
71
|
-
|
|
72
|
-
|
|
76
|
+
let titulo = tituloCard;
|
|
77
|
+
if (!titulo) {
|
|
78
|
+
titulo = await hacerPreguntaTrim('Ingrese el título para la tarjeta: ');
|
|
79
|
+
}
|
|
80
|
+
let descripcion = descripcionCard;
|
|
81
|
+
if (!descripcion) {
|
|
82
|
+
descripcion = await hacerPreguntaTrim('Ingrese la descripción para la tarjeta: ');
|
|
83
|
+
}
|
|
73
84
|
|
|
74
85
|
const nombreLower = nombre.toLowerCase();
|
|
75
86
|
const nombreCapitalizado = nombre.charAt(0).toUpperCase() + nombre.slice(1);
|
|
@@ -81,13 +92,13 @@ export async function cloneFrontendComponent() {
|
|
|
81
92
|
|
|
82
93
|
if (!fs.existsSync(rutaFuente)) {
|
|
83
94
|
console.error(`No se encontró la carpeta plantilla en: ${rutaFuente}`);
|
|
84
|
-
closeReadLine();
|
|
95
|
+
if (opciones.cerrarAlFinalizar) closeReadLine();
|
|
85
96
|
process.exit(1);
|
|
86
97
|
}
|
|
87
98
|
|
|
88
99
|
if (fs.existsSync(rutaDestino)) {
|
|
89
100
|
console.error(`La carpeta destino ya existe: ${rutaDestino}`);
|
|
90
|
-
closeReadLine();
|
|
101
|
+
if (opciones.cerrarAlFinalizar) closeReadLine();
|
|
91
102
|
process.exit(1);
|
|
92
103
|
}
|
|
93
104
|
|
|
@@ -108,8 +119,16 @@ export async function cloneFrontendComponent() {
|
|
|
108
119
|
copiarYReemplazar(rutaFuente, rutaDestino);
|
|
109
120
|
} else if (entrada.isFile()) {
|
|
110
121
|
let contenido = fs.readFileSync(rutaFuente, 'utf-8');
|
|
122
|
+
|
|
123
|
+
// Reemplazos base
|
|
111
124
|
contenido = contenido.replace(/GestionTablaXYZComponent/g, nombreClase);
|
|
112
125
|
contenido = contenido.replace(/XYZ/g, nombreLower);
|
|
126
|
+
|
|
127
|
+
// Si tenemos información de la tabla, personalizamos el componente
|
|
128
|
+
if (tableInfo && entrada.name.endsWith('.ts')) {
|
|
129
|
+
contenido = personalizarComponenteFrontend(contenido, tableInfo, nombreLower);
|
|
130
|
+
}
|
|
131
|
+
|
|
113
132
|
fs.writeFileSync(rutaDestino, contenido);
|
|
114
133
|
}
|
|
115
134
|
});
|
|
@@ -174,7 +193,42 @@ export async function cloneFrontendComponent() {
|
|
|
174
193
|
} else {
|
|
175
194
|
console.error(`No se encontró el archivo HTML en: ${rutaHtml}`);
|
|
176
195
|
}
|
|
177
|
-
|
|
196
|
+
if (opciones.cerrarAlFinalizar) {
|
|
197
|
+
closeReadLine();
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function personalizarComponenteFrontend(contenido, tableInfo, nombreServicioLower) {
|
|
202
|
+
const { columns, primaryKey } = tableInfo;
|
|
203
|
+
|
|
204
|
+
// 1. Reemplazar columnas
|
|
205
|
+
const nuevasColumnas = columns.map(col => {
|
|
206
|
+
return ` { Llave: '${col.name}', Alias: '${col.alias}', TipoDeFiltro: 'Tabla' }`;
|
|
207
|
+
}).join(',\n');
|
|
208
|
+
|
|
209
|
+
contenido = contenido.replace(/public columnas = \[\s*[\s\S]*?\];/, `public columnas = [\n${nuevasColumnas}\n ];`);
|
|
210
|
+
|
|
211
|
+
// 2. Reemplazar LicenciaId (la PK de la plantilla) por la PK real
|
|
212
|
+
const pkRegex = new RegExp('LicenciaId', 'g');
|
|
213
|
+
contenido = contenido.replace(pkRegex, primaryKey);
|
|
214
|
+
|
|
215
|
+
// 3. Reemplazar endpoints (ej: licencias/listar por servicio/listar)
|
|
216
|
+
const endpointRegex = new RegExp('licencias/', 'g');
|
|
217
|
+
contenido = contenido.replace(endpointRegex, `${nombreServicioLower}/`);
|
|
218
|
+
|
|
219
|
+
return contenido;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
console.log('Tarjeta agregada exitosamente.');
|
|
223
|
+
} else {
|
|
224
|
+
console.error('No se encontró un </div> de cierre en el HTML del contenedor principal.');
|
|
225
|
+
}
|
|
226
|
+
} else {
|
|
227
|
+
console.error(`No se encontró el archivo HTML en: ${rutaHtml}`);
|
|
228
|
+
}
|
|
229
|
+
if (opciones.cerrarAlFinalizar) {
|
|
230
|
+
closeReadLine();
|
|
231
|
+
}
|
|
178
232
|
}
|
|
179
233
|
|
|
180
234
|
export function showFrontendVersion() {
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { updateBackend } from './backend.js';
|
|
4
|
+
import { updateFrontend } from './frontend.js';
|
|
5
|
+
import { closeReadLine } from '../utils/index.js';
|
|
6
|
+
import { execSync } from 'child_process';
|
|
7
|
+
|
|
8
|
+
export async function runGlobalUpdate() {
|
|
9
|
+
const currentDir = process.cwd();
|
|
10
|
+
const entries = fs.readdirSync(currentDir, { withFileTypes: true });
|
|
11
|
+
|
|
12
|
+
const directories = entries.filter(entry => entry.isDirectory());
|
|
13
|
+
|
|
14
|
+
if (directories.length === 0) {
|
|
15
|
+
console.log('No se encontraron directorios en la carpeta actual.');
|
|
16
|
+
closeReadLine();
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
let foundSomething = false;
|
|
21
|
+
|
|
22
|
+
for (const dir of directories) {
|
|
23
|
+
const dirName = dir.name.toLowerCase();
|
|
24
|
+
const fullPath = path.join(currentDir, dir.name);
|
|
25
|
+
|
|
26
|
+
if (dirName.includes('back') || dirName.includes('front')) {
|
|
27
|
+
const type = dirName.includes('back') ? 'Backend' : 'Frontend';
|
|
28
|
+
console.log(`\n=========================================`);
|
|
29
|
+
console.log(`Procesando ${type} en: ${dir.name}`);
|
|
30
|
+
console.log(`=========================================\n`);
|
|
31
|
+
|
|
32
|
+
process.chdir(fullPath);
|
|
33
|
+
|
|
34
|
+
// Execute git pull if it's a git repository
|
|
35
|
+
if (fs.existsSync(path.join(fullPath, '.git'))) {
|
|
36
|
+
console.log(`Ejecutando git pull en ${dir.name}...`);
|
|
37
|
+
try {
|
|
38
|
+
execSync('git pull', { stdio: 'inherit' });
|
|
39
|
+
} catch (error) {
|
|
40
|
+
console.error(`Error al ejecutar git pull en ${dir.name}. Continuando con la actualización...`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
if (dirName.includes('back')) {
|
|
46
|
+
await updateBackend({ cerrarAlFinalizar: false });
|
|
47
|
+
} else if (dirName.includes('front')) {
|
|
48
|
+
await updateFrontend({ cerrarAlFinalizar: false });
|
|
49
|
+
|
|
50
|
+
// Execute build for frontend if package.json exists
|
|
51
|
+
if (fs.existsSync(path.join(process.cwd(), 'package.json'))) {
|
|
52
|
+
console.log(`\nEjecutando sudo npm run build en ${dir.name}...`);
|
|
53
|
+
try {
|
|
54
|
+
execSync('sudo npm run build', { stdio: 'inherit' });
|
|
55
|
+
} catch (error) {
|
|
56
|
+
console.error(`Error al ejecutar build en ${dir.name}. Continuando...`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
foundSomething = true;
|
|
61
|
+
} catch (error) {
|
|
62
|
+
console.error(`Error actualizando ${type} en ${dir.name}:`, error);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
process.chdir(currentDir);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (!foundSomething) {
|
|
70
|
+
console.log('No se encontraron carpetas con "back" o "front" en el nombre.');
|
|
71
|
+
} else {
|
|
72
|
+
console.log('\nProceso de actualización global finalizado.');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
closeReadLine();
|
|
76
|
+
}
|
package/index.js
CHANGED
|
@@ -4,9 +4,11 @@ import { Command } from 'commander';
|
|
|
4
4
|
import { mostrarVersion, getPackageInfo, closeReadLine } from './utils/index.js';
|
|
5
5
|
import path from 'path';
|
|
6
6
|
import { fileURLToPath } from 'url';
|
|
7
|
-
import { initDb, showDbVersion } from './commands/db.js';
|
|
7
|
+
import { initDb, addTableDb, showDbVersion } from './commands/db.js';
|
|
8
8
|
import { initBackend, updateBackend, addServiceBackend, showBackendVersion } from './commands/backend.js';
|
|
9
9
|
import { initFrontend, updateFrontend, cloneFrontendComponent, showFrontendVersion } from './commands/frontend.js';
|
|
10
|
+
import { runGlobalUpdate } from './commands/update.js';
|
|
11
|
+
import { createComponent } from './commands/createComponent.js';
|
|
10
12
|
import { runCommit } from './commands/commit.js';
|
|
11
13
|
|
|
12
14
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -30,16 +32,20 @@ if (packageInfo) {
|
|
|
30
32
|
|
|
31
33
|
// Define 'db' command
|
|
32
34
|
program.command('db')
|
|
35
|
+
.alias('bd')
|
|
33
36
|
.description('Comandos para la gestión de plantillas de base de datos.')
|
|
34
37
|
.option('--init', 'Inicializa un nuevo proyecto de base de datos.')
|
|
38
|
+
.option('--add-table', 'Agrega una nueva tabla al archivo de estructura.')
|
|
35
39
|
.option('--version', 'Muestra la versión del módulo de base de datos.')
|
|
36
40
|
.action(async (options) => {
|
|
37
41
|
if (options.init) {
|
|
38
42
|
await initDb();
|
|
43
|
+
} else if (options.addTable) {
|
|
44
|
+
await addTableDb();
|
|
39
45
|
} else if (options.version) {
|
|
40
46
|
showDbVersion();
|
|
41
47
|
} else {
|
|
42
|
-
console.log('Uso: utn db [--init | --version]');
|
|
48
|
+
console.log('Uso: utn db [--init | --add-table | --version]');
|
|
43
49
|
closeReadLine();
|
|
44
50
|
}
|
|
45
51
|
});
|
|
@@ -88,6 +94,20 @@ program.command('frontend')
|
|
|
88
94
|
}
|
|
89
95
|
});
|
|
90
96
|
|
|
97
|
+
// Define 'update' command
|
|
98
|
+
program.command('update')
|
|
99
|
+
.description('Recorre los directorios y actualiza proyectos de backend (si el nombre contiene "back") y frontend (si el nombre contiene "front").')
|
|
100
|
+
.action(async () => {
|
|
101
|
+
await runGlobalUpdate();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// Define 'create-component' command
|
|
105
|
+
program.command('create-component')
|
|
106
|
+
.description('Crea un nuevo servicio en el backend y un componente en el frontend simultáneamente.')
|
|
107
|
+
.action(async () => {
|
|
108
|
+
await createComponent();
|
|
109
|
+
});
|
|
110
|
+
|
|
91
111
|
// Define 'commit' command
|
|
92
112
|
program.command('commit')
|
|
93
113
|
.description('Realiza un commit siguiendo el flujo de trabajo predefinido.')
|
package/package.json
CHANGED
package/templates/frontend/src/app/Paginas/gestion-tabla-XYZ/gestion-tabla-XYZ.component.html
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
[subAccionesSiempreClickeable] = "subAccionesSiempreClickeable"
|
|
20
20
|
[subAccionesDinamicas] = "subAccionesDinamicas"
|
|
21
21
|
[desplegable] = "false"
|
|
22
|
-
[atributoDesplegable] = ""
|
|
22
|
+
[atributoDesplegable] = "''"
|
|
23
23
|
[mostrarElBotonDeGuardado] = "true"
|
|
24
24
|
[mostrarElBotonDeDescargar] = "true"
|
|
25
25
|
[mostrarElBotonDeFiltro] = "true"
|