@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.
- package/.lh/.lhignore +6 -0
- package/.lh/package.json.json +26 -0
- package/package.json +25 -0
- package/src/commands/run/database/create/addDatabaseConfig.js +193 -0
- package/src/commands/run/database/create/createDatabase.js +88 -0
- package/src/commands/run/database/create/index.js +157 -0
- package/src/commands/run/seeder/add.js +76 -0
- package/src/commands/run/table/fresh.js +76 -0
- package/src/commands/run/table/refresh.js +76 -0
- package/src/commands/run/trigger/fresh.js +76 -0
- package/src/index.js +100 -0
- package/src/lib/DBCubeLogger.js +116 -0
- package/src/lib/LoggerConsole.js +262 -0
- package/src/utils/Config.js +49 -0
- package/src/utils/ConfigFileUtils.js +295 -0
- package/src/utils/FileUtils.js +73 -0
|
@@ -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;
|