@dbcube/cli 1.0.1

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,295 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ /**
5
+ * Clase para manejar operaciones sobre el archivo de configuración
6
+ */
7
+ class ConfigFileUtils {
8
+ /**
9
+ * Obtiene la ruta al archivo de configuración
10
+ * @returns {string} - Ruta al archivo de configuración
11
+ */
12
+ static getConfigPath() {
13
+ // Usar la ruta raíz del proyecto
14
+ const rootPath = path.resolve(process.cwd());
15
+ const configPath = path.join(rootPath, 'dbcube.config.js');
16
+ return configPath;
17
+ }
18
+
19
+ /**
20
+ * Obtiene los nombres de las bases de datos configuradas en dbcube.config.js
21
+ * @returns {Promise<Array<string>>} - Array con los nombres de las bases de datos
22
+ */
23
+ static async getConfiguredDatabases() {
24
+ try {
25
+ // Ruta al archivo de configuración
26
+ const configPath = this.getConfigPath();
27
+
28
+ // Comprobar si el archivo existe
29
+ if (!fs.existsSync(configPath)) {
30
+ ConfigFileUtils.crearArchivoConfig(configPath);
31
+ }
32
+
33
+ return this.loadDatabasesFromPath(configPath);
34
+ } catch (error) {
35
+ throw new Error('Error al obtener las bases de datos configuradas:', error);
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Carga las bases de datos desde una ruta específica
41
+ * @param {string} configPath - Ruta al archivo de configuración
42
+ * @returns {Array<string>} - Nombres de las bases de datos
43
+ */
44
+ static loadDatabasesFromPath(configPath) {
45
+ try {
46
+ // Borrar la caché del módulo para asegurarnos de cargar la versión más reciente
47
+ delete require.cache[require.resolve(configPath)];
48
+
49
+ // Cargar el archivo de configuración
50
+ const config = require(configPath);
51
+
52
+ // Crear una instancia temporal de Config para obtener los datos
53
+ const tempConfig = {
54
+ databases: {},
55
+ data: {},
56
+ set: function(configData) {
57
+ this.data = configData;
58
+ if (configData.databases) {
59
+ this.databases = configData.databases;
60
+ }
61
+ },
62
+ getAllDatabases: function() {
63
+ return this.databases;
64
+ }
65
+ };
66
+
67
+ // Ejecutar la función de configuración
68
+ config(tempConfig);
69
+
70
+ // Obtener los nombres de las bases de datos
71
+ const databaseNames = Object.keys(tempConfig.getAllDatabases());
72
+ const arrayDatabases = [];
73
+ for (const database of databaseNames) {
74
+ arrayDatabases.push({
75
+ name: database,
76
+ type: tempConfig.getAllDatabases()[database].type
77
+ });
78
+ }
79
+ return arrayDatabases;
80
+ } catch (error) {
81
+ console.error('Error al cargar las bases de datos desde ' + configPath, error);
82
+ return ['test1']; // Valor por defecto
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Agrega una nueva configuración de base de datos al archivo de configuración
88
+ * @param {string} databaseName - Nombre de la base de datos
89
+ * @param {string} databaseType - Tipo de la base de datos (mysql, postgresql, etc.)
90
+ * @param {Object} databaseConfig - Configuración de la base de datos
91
+ * @returns {Promise<boolean>} - Retorna true si se agregó correctamente
92
+ */
93
+ static async addDatabaseConfig(databaseName, databaseType, databaseConfig) {
94
+ try {
95
+ // Ruta al archivo de configuración
96
+ const configPath = this.getConfigPath();
97
+
98
+ // Verificar si el archivo existe
99
+ if (!fs.existsSync(configPath)) {
100
+ ConfigFileUtils.crearArchivoConfig(configPath);
101
+ }
102
+
103
+ // Leer el archivo de configuración
104
+ let configContent = fs.readFileSync(configPath, 'utf8');
105
+
106
+ // Convertir los valores de PORT a número si es necesario
107
+ if (databaseConfig.PORT && typeof databaseConfig.PORT === 'string') {
108
+ databaseConfig.PORT = parseInt(databaseConfig.PORT, 10);
109
+ }
110
+
111
+ // Crear el texto de la nueva configuración
112
+ const newDatabaseConfigText = this.createDatabaseConfigText(databaseName, databaseType, databaseConfig);
113
+
114
+ // Buscar la posición donde insertar la nueva configuración
115
+ const insertPosition = configContent.indexOf('databases: {') + 'databases: {'.length;
116
+ if (insertPosition === -1 + 'databases: {'.length) {
117
+ throw new Error('No se encontró la sección de bases de datos en el archivo de configuración');
118
+ }
119
+
120
+ // Insertar la nueva configuración
121
+ configContent = configContent.slice(0, insertPosition) +
122
+ newDatabaseConfigText +
123
+ configContent.slice(insertPosition);
124
+
125
+ // Escribir el archivo actualizado
126
+ fs.writeFileSync(configPath, configContent, 'utf8');
127
+
128
+ return true;
129
+ } catch (error) {
130
+ console.error('\nError al agregar la configuración de la base de datos:', error);
131
+ return false;
132
+ }
133
+ }
134
+
135
+ /**
136
+ * Crea el texto de configuración para una base de datos
137
+ * @param {string} databaseName - Nombre de la base de datos
138
+ * @param {string} databaseType - Tipo de la base de datos
139
+ * @param {Object} databaseConfig - Configuración de la base de datos
140
+ * @returns {string} - Texto de configuración formateado
141
+ */
142
+ static createDatabaseConfigText(databaseName, databaseType, databaseConfig) {
143
+ if(databaseType=="sqlite"){
144
+ const arrayDataEnv = [
145
+ "DBCUBE_"+databaseName.toUpperCase()+"_DATABASE"+"="+databaseConfig.DATABASE
146
+ ];
147
+ ConfigFileUtils.updateDotenv(arrayDataEnv)
148
+ return `
149
+ ${databaseName}: {
150
+ type: "${databaseType}",
151
+ config:{
152
+ DATABASE: process.env.${arrayDataEnv[0].split("=")[0]}
153
+ }
154
+ },`;
155
+ }else{
156
+ const arrayDataEnv = [
157
+ "DBCUBE_"+databaseName.toUpperCase()+"_HOST"+"="+databaseConfig.HOST,
158
+ "DBCUBE_"+databaseName.toUpperCase()+"_PORT"+"="+databaseConfig.PORT,
159
+ "DBCUBE_"+databaseName.toUpperCase()+"_DATABASE"+"="+databaseConfig.DATABASE,
160
+ "DBCUBE_"+databaseName.toUpperCase()+"_USER"+"="+databaseConfig.USER,
161
+ "DBCUBE_"+databaseName.toUpperCase()+"_PASSWORD"+"="+databaseConfig.PASSWORD,
162
+ ];
163
+ ConfigFileUtils.updateDotenv(arrayDataEnv)
164
+ return `
165
+ ${databaseName}: {
166
+ type: "${databaseType}",
167
+ config:{
168
+ HOST: process.env.${arrayDataEnv[0].split("=")[0]},
169
+ USER: process.env.${arrayDataEnv[3].split("=")[0]},
170
+ PASSWORD: process.env.${arrayDataEnv[4].split("=")[0]},
171
+ DATABASE: process.env.${arrayDataEnv[2].split("=")[0]},
172
+ PORT: parseInt(process.env.${arrayDataEnv[1].split("=")[0]}, 10)
173
+ }
174
+ },`;
175
+ }
176
+ }
177
+
178
+ /**
179
+ * Crea o actualiza un archivo .env con un array de strings tipo "KEY=VALUE"
180
+ * @param {string} ruta - Ruta donde se creará o modificará el archivo `.env`
181
+ * @param {string[]} entries - Array de strings tipo "KEY=VALUE"
182
+ */
183
+ static updateDotenv(entries) {
184
+ const rutaAbsoluta = path.resolve(process.cwd(), '.env');
185
+ let contenidoExistente = '';
186
+
187
+ try {
188
+ // Verificar si la ruta existe y qué tipo de archivo es
189
+ if (fs.existsSync(rutaAbsoluta)) {
190
+ const stats = fs.statSync(rutaAbsoluta);
191
+
192
+ if (stats.isDirectory()) {
193
+ // Si .env es un directorio, elimínalo y crea el archivo
194
+ console.warn(`⚠️ Se encontró un directorio llamado .env, eliminándolo para crear el archivo`);
195
+ fs.rmSync(rutaAbsoluta, { recursive: true, force: true });
196
+ } else if (stats.isFile()) {
197
+ // Si es un archivo, leer su contenido
198
+ contenidoExistente = fs.readFileSync(rutaAbsoluta, 'utf8');
199
+ }
200
+ }
201
+ } catch (error) {
202
+ if (error.code === 'EISDIR') {
203
+ console.warn(`⚠️ Conflicto detectado: .env existe como directorio, eliminándolo`);
204
+ try {
205
+ fs.rmSync(rutaAbsoluta, { recursive: true, force: true });
206
+ } catch (removeError) {
207
+ console.error(`❌ Error al eliminar directorio .env:`, removeError.message);
208
+ throw removeError;
209
+ }
210
+ } else {
211
+ console.error(`❌ Error al leer .env:`, error.message);
212
+ throw error;
213
+ }
214
+ }
215
+
216
+ // Procesar contenido existente
217
+ const lineas = contenidoExistente ? contenidoExistente.split('\n') : [];
218
+ const mapaEnv = {};
219
+
220
+ // Construir mapa de claves actuales
221
+ for (let linea of lineas) {
222
+ const match = linea.match(/^([\w.-]+)=(.*)$/);
223
+ if (match) {
224
+ const [, key, value] = match;
225
+ mapaEnv[key] = value;
226
+ }
227
+ }
228
+
229
+ // Procesar nuevas entradas
230
+ for (const entry of entries) {
231
+ const [key, ...valueParts] = entry.split('=');
232
+ const value = valueParts.join('='); // En caso de que el valor contenga '='
233
+
234
+ if (!key || value === undefined) {
235
+ console.warn(`⚠️ Entrada inválida ignorada: ${entry}`);
236
+ continue;
237
+ }
238
+
239
+ if (!mapaEnv.hasOwnProperty(key)) {
240
+ // Agregar nueva línea si no existe
241
+ lineas.push(`${key}=${value}`);
242
+ console.log(`➕ Agregando nueva variable: ${key}`);
243
+ } else if (mapaEnv[key] !== value) {
244
+ // Actualizar línea existente
245
+ const index = lineas.findIndex(line => line.startsWith(`${key}=`));
246
+ if (index !== -1) {
247
+ lineas[index] = `${key}=${value}`;
248
+ console.log(`🔄 Actualizando variable: ${key}`);
249
+ }
250
+ }
251
+ // Si existe y tiene el mismo valor, no se hace nada
252
+ }
253
+
254
+ try {
255
+ // Filtrar líneas vacías al final y asegurar que termine con salto de línea
256
+ const contenidoFinal = lineas.filter(linea => linea.trim() !== '').join('\n');
257
+
258
+ // Escribir de nuevo el archivo .env
259
+ fs.writeFileSync(rutaAbsoluta, contenidoFinal + '\n', 'utf8');
260
+ console.log(`✅ .env actualizado en: ${rutaAbsoluta}`);
261
+
262
+ } catch (error) {
263
+ console.error(`❌ Error al escribir .env:`, error.message);
264
+ throw error;
265
+ }
266
+ }
267
+
268
+ /**
269
+ * Crea un archivo de configuración base para dbcube en la ruta especificada.
270
+ * @param {string} rutaDestino - Ruta completa donde se debe crear el archivo.
271
+ */
272
+ static crearArchivoConfig(rutaDestino) {
273
+ const contenido = `require('dotenv').config({ quiet: true });
274
+
275
+ module.exports = function (config) {
276
+ config.set({
277
+ databases: {
278
+ }
279
+ });
280
+ };
281
+ `;
282
+
283
+ // Asegurarse de que el directorio existe
284
+ const dir = path.dirname(rutaDestino);
285
+ if (!fs.existsSync(dir)) {
286
+ fs.mkdirSync(dir, { recursive: true });
287
+ }
288
+
289
+ // Escribir archivo
290
+ fs.writeFileSync(rutaDestino, contenido, 'utf8');
291
+ //console.log(`\n✅ Archivo creado en: ${rutaDestino}`);
292
+ }
293
+ }
294
+
295
+ module.exports = ConfigFileUtils;
@@ -0,0 +1,73 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ class FileUtils {
5
+ /**
6
+ * Verifica si un archivo existe (asincrónico).
7
+ * @param {string} filePath - Ruta del archivo.
8
+ * @returns {Promise<boolean>} - True si el archivo existe, false si no.
9
+ */
10
+ static async fileExists(filePath) {
11
+ return new Promise((resolve) => {
12
+ fs.access(path.resolve(filePath), fs.constants.F_OK, (err) => {
13
+ resolve(!err);
14
+ });
15
+ });
16
+ }
17
+
18
+ /**
19
+ * Verifica si un archivo existe (sincrónico).
20
+ * @param {string} filePath - Ruta del archivo.
21
+ * @returns {boolean} - True si el archivo existe, false si no.
22
+ */
23
+ static fileExistsSync(filePath) {
24
+ try {
25
+ fs.accessSync(path.resolve(filePath), fs.constants.F_OK);
26
+ return true;
27
+ } catch {
28
+ return false;
29
+ }
30
+ }
31
+
32
+ static extractDatabaseName(input) {
33
+ const match = input.match(/@database\(["']?([\w-]+)["']?\)/);
34
+ return match ? match[1] : null;
35
+ }
36
+
37
+ /**
38
+ * Lee recursivamente archivos que terminan en un sufijo dado y los ordena numéricamente.
39
+ * @param {string} dir - Directorio base (relativo o absoluto).
40
+ * @param {string} suffix - Sufijo de archivo (como 'table.cube').
41
+ * @returns {string[]} - Rutas absolutas de los archivos encontrados y ordenados.
42
+ */
43
+ static getCubeFilesRecursively(dir, suffix) {
44
+ const baseDir = path.resolve(dir); // ✅ Asegura que sea absoluto
45
+ const cubeFiles = [];
46
+
47
+ function recurse(currentDir) {
48
+ const entries = fs.readdirSync(currentDir, { withFileTypes: true });
49
+
50
+ for (const entry of entries) {
51
+ const fullPath = path.join(currentDir, entry.name);
52
+ if (entry.isDirectory()) {
53
+ recurse(fullPath);
54
+ } else if (entry.isFile() && entry.name.endsWith(suffix)) {
55
+ cubeFiles.push(fullPath); // Ya es absoluta
56
+ }
57
+ }
58
+ }
59
+
60
+ recurse(baseDir);
61
+
62
+ // Ordenar por número si los archivos comienzan con un número
63
+ cubeFiles.sort((a, b) => {
64
+ const aNum = parseInt(path.basename(a));
65
+ const bNum = parseInt(path.basename(b));
66
+ return (isNaN(aNum) ? 0 : aNum) - (isNaN(bNum) ? 0 : bNum);
67
+ });
68
+
69
+ return cubeFiles;
70
+ }
71
+ }
72
+
73
+ module.exports = FileUtils;