@dbcube/cli 5.2.4 → 5.2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dbcube/cli",
3
- "version": "5.2.4",
3
+ "version": "5.2.5",
4
4
  "main": "src/index.js",
5
5
  "scripts": {
6
6
  "dbcube": "node src/index.js"
@@ -28,11 +28,11 @@
28
28
  "access": "public"
29
29
  },
30
30
  "dependencies": {
31
- "@dbcube/schema-builder": "^5.2.4",
31
+ "@dbcube/schema-builder": "^5.2.5",
32
32
  "@inquirer/prompts": "^8.5.2",
33
33
  "alwait": "^1.0.0",
34
34
  "chalk": "4.1.2",
35
- "dbcube": "^5.2.4",
35
+ "dbcube": "^5.2.5",
36
36
  "dotenv": "^17.4.2",
37
37
  "fs-extra": "^11.3.5",
38
38
  "glob": "^13.0.6",
@@ -21,13 +21,14 @@ async function main() {
21
21
  if (major >= 16) ok(`Node.js ${process.versions.node}`);
22
22
  else { fail(`Node.js ${process.versions.node} — DBCube requires >= 16`); problems++; }
23
23
 
24
- // 2. Config file
25
- const configPath = path.join(process.cwd(), 'dbcube.config.js');
24
+ // 2. Config file (.js o .cjs — getConfigPath prefiere .cjs en proyectos ESM)
25
+ const configPath = ConfigFileUtils.getConfigPath();
26
+ const configName = path.basename(configPath);
26
27
  if (!fs.existsSync(configPath)) {
27
- fail('dbcube.config.js not found — run: npx dbcube init');
28
+ fail(`${configName} not found — run: npx dbcube init`);
28
29
  problems++;
29
30
  } else {
30
- ok('dbcube.config.js found');
31
+ ok(`${configName} found`);
31
32
  }
32
33
 
33
34
  // 3. Configured databases
@@ -11,13 +11,14 @@ async function main() {
11
11
  const root = process.cwd();
12
12
  console.log(`\n💚 ${chalk.green('Initializing DBCube project...')}\n`);
13
13
 
14
- // 1. dbcube.config.js
15
- const configPath = path.join(root, 'dbcube.config.js');
14
+ // 1. dbcube.config.(js|cjs) — getConfigPath elige .cjs en proyectos ESM
15
+ const configPath = ConfigFileUtils.getConfigPath();
16
+ const configName = path.basename(configPath);
16
17
  if (fs.existsSync(configPath)) {
17
- console.log(` ${chalk.yellow('•')} dbcube.config.js already exists — skipped`);
18
+ console.log(` ${chalk.yellow('•')} ${configName} already exists — skipped`);
18
19
  } else {
19
20
  ConfigFileUtils.crearArchivoConfig(configPath);
20
- console.log(` ${chalk.green('✓')} dbcube.config.js created`);
21
+ console.log(` ${chalk.green('✓')} ${configName} created`);
21
22
  }
22
23
 
23
24
  // 2. dbcube/ directory
@@ -21,8 +21,7 @@ async function addDatabaseConfiguration(databaseName = null, motor = null) {
21
21
  databaseName = await input({
22
22
  message: 'Nombre de referencia a la base de datos:',
23
23
  validate: (value) => {
24
- const rootPath = path.resolve(process.cwd());
25
- const configPath = path.join(rootPath, 'dbcube.config.js');
24
+ const configPath = ConfigFileUtils.getConfigPath();
26
25
  const getconfig = require(configPath);
27
26
  const config = new Config();
28
27
  getconfig(config);
@@ -1,5 +1,22 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
+ const EnvLoader = require('./EnvLoader');
4
+
5
+ /**
6
+ * Detecta si el proyecto está marcado como ESM ("type": "module" en
7
+ * package.json), lo que hace que dbcube.config.js (CommonJS) no se pueda
8
+ * cargar con require(). Devuelve true si es ESM.
9
+ */
10
+ function projectIsESM() {
11
+ try {
12
+ const pkgPath = path.resolve(process.cwd(), 'package.json');
13
+ if (!fs.existsSync(pkgPath)) return false;
14
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
15
+ return pkg.type === 'module';
16
+ } catch {
17
+ return false;
18
+ }
19
+ }
3
20
 
4
21
  /**
5
22
  * Clase para manejar operaciones sobre el archivo de configuración
@@ -10,10 +27,15 @@ class ConfigFileUtils {
10
27
  * @returns {string} - Ruta al archivo de configuración
11
28
  */
12
29
  static getConfigPath() {
13
- // Usar la ruta raíz del proyecto
14
30
  const rootPath = path.resolve(process.cwd());
15
- const configPath = path.join(rootPath, 'dbcube.config.js');
16
- return configPath;
31
+ const cjsPath = path.join(rootPath, 'dbcube.config.cjs');
32
+ const jsPath = path.join(rootPath, 'dbcube.config.js');
33
+ // Si ya existe alguno, ese manda (preferimos .cjs).
34
+ if (fs.existsSync(cjsPath)) return cjsPath;
35
+ if (fs.existsSync(jsPath)) return jsPath;
36
+ // Si no existe ninguno (se va a crear): en proyectos ESM
37
+ // ("type": "module") debe ser .cjs para poder cargarse con require().
38
+ return projectIsESM() ? cjsPath : jsPath;
17
39
  }
18
40
 
19
41
  /**
@@ -21,19 +43,16 @@ class ConfigFileUtils {
21
43
  * @returns {Promise<Array<string>>} - Array con los nombres de las bases de datos
22
44
  */
23
45
  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);
46
+ // Sin try/catch que oculte el error: loadDatabasesFromPath ya lanza
47
+ // mensajes explícitos y accionables. (Antes, `new Error('msg', error)`
48
+ // descartaba el segundo argumento y se perdía la causa real.)
49
+ const configPath = this.getConfigPath();
50
+
51
+ if (!fs.existsSync(configPath)) {
52
+ ConfigFileUtils.crearArchivoConfig(configPath);
36
53
  }
54
+
55
+ return this.loadDatabasesFromPath(configPath);
37
56
  }
38
57
 
39
58
  /**
@@ -42,46 +61,91 @@ class ConfigFileUtils {
42
61
  * @returns {Array<string>} - Nombres de las bases de datos
43
62
  */
44
63
  static loadDatabasesFromPath(configPath) {
64
+ // Carga automática de .env (cargador propio de DBCube): el usuario no
65
+ // necesita instalar dotenv ni escribir require("dotenv") en su config.
66
+ EnvLoader.load();
67
+
68
+ let config;
45
69
  try {
46
70
  // Borrar la caché del módulo para asegurarnos de cargar la versión más reciente
47
71
  delete require.cache[require.resolve(configPath)];
48
-
49
72
  // 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;
73
+ config = require(configPath);
74
+ } catch (error) {
75
+ const missing = (error.message.match(/Cannot find module '([^']+)'/) || [])[1];
76
+ // ¿El módulo que no se encuentra es el PROPIO config? Entonces el
77
+ // problema es de carga (típicamente ESM). Si es OTRO módulo, es una
78
+ // dependencia del config que falta (p. ej. dotenv).
79
+ const missingIsConfig = missing && (configPath.includes(missing) || missing.includes('dbcube.config'));
80
+
81
+ // Caso 1: falta una dependencia que el config requiere.
82
+ if (error.code === 'MODULE_NOT_FOUND' && missing && !missingIsConfig) {
83
+ throw new Error(
84
+ `dbcube.config.js requiere un módulo que no está instalado: "${missing}".\n` +
85
+ ` Instálalo (p. ej. npm install ${missing}).\n` +
86
+ ` Nota: DBCube ya carga el .env automáticamente — puedes quitar require("dotenv") de tu config.`
87
+ );
88
+ }
89
+ // Caso 2: proyecto ESM ("type": "module") → require() del config falla
90
+ // o el .js se interpreta como módulo ES y `module.exports` no aplica.
91
+ if (projectIsESM() || error.code === 'ERR_REQUIRE_ESM') {
92
+ throw new Error(
93
+ `No se pudo cargar dbcube.config.js porque el proyecto está marcado como ESM ` +
94
+ `("type": "module" en package.json), pero la configuración usa CommonJS (module.exports).\n` +
95
+ ` Solución: renombra el archivo a "dbcube.config.cjs", o quita "type": "module" de package.json.`
96
+ );
97
+ }
98
+ // Cualquier otro error de carga: mostrarlo tal cual.
99
+ throw new Error(`No se pudo cargar la configuración desde ${configPath}:\n ${error.message}`);
100
+ }
101
+
102
+ // Validar que la config exporte una función (firma esperada).
103
+ if (typeof config !== 'function') {
104
+ const hint = projectIsESM()
105
+ ? ` El proyecto es ESM ("type": "module"): renombra el archivo a "dbcube.config.cjs" o quita "type": "module".`
106
+ : ` Debe exportar una función: module.exports = function (config) { config.set({ databases: {...} }); }.`;
107
+ throw new Error(`dbcube.config.js no exporta una configuración válida.${hint}`);
108
+ }
109
+
110
+ // Crear una instancia temporal de Config para obtener los datos
111
+ const tempConfig = {
112
+ databases: {},
113
+ data: {},
114
+ set: function(configData) {
115
+ this.data = configData;
116
+ if (configData.databases) {
117
+ this.databases = configData.databases;
64
118
  }
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
- });
119
+ },
120
+ getAllDatabases: function() {
121
+ return this.databases;
78
122
  }
79
- return arrayDatabases;
123
+ };
124
+
125
+ // Ejecutar la función de configuración
126
+ try {
127
+ config(tempConfig);
80
128
  } catch (error) {
81
- // Fallar claro: devolver una DB inventada ocultaría el problema
82
- // y haría que los comandos operen contra una base inexistente
83
- throw new Error(`No se pudo cargar la configuración desde ${configPath}: ${error.message}`);
129
+ throw new Error(`dbcube.config.js lanzó un error al evaluarse:\n ${error.message}`);
130
+ }
131
+
132
+ const allDatabases = tempConfig.getAllDatabases();
133
+ if (!allDatabases || Object.keys(allDatabases).length === 0) {
134
+ throw new Error(
135
+ `dbcube.config.js no define ninguna base de datos.\n` +
136
+ ` Añade al menos una en config.set({ databases: { miBase: { type, config } } }).`
137
+ );
84
138
  }
139
+
140
+ const databaseNames = Object.keys(allDatabases);
141
+ const arrayDatabases = [];
142
+ for (const database of databaseNames) {
143
+ arrayDatabases.push({
144
+ name: database,
145
+ type: allDatabases[database].type
146
+ });
147
+ }
148
+ return arrayDatabases;
85
149
  }
86
150
 
87
151
  /**
@@ -271,15 +335,16 @@ class ConfigFileUtils {
271
335
  * @param {string} rutaDestino - Ruta completa donde se debe crear el archivo.
272
336
  */
273
337
  static crearArchivoConfig(rutaDestino) {
274
- const contenido = `require('dotenv').config({ quiet: true });
275
-
276
- module.exports = function (config) {
277
- config.set({
278
- databases: {
279
- }
280
- });
281
- };
282
- `;
338
+ // DBCube carga el .env automáticamente, así que el template ya NO
339
+ // necesita require('dotenv') (que además requeriría instalar el paquete).
340
+ const contenido = `// DBCube carga .env automáticamente — usa process.env.MI_VAR sin dotenv.
341
+ module.exports = function (config) {
342
+ config.set({
343
+ databases: {
344
+ }
345
+ });
346
+ };
347
+ `;
283
348
 
284
349
  // Asegurarse de que el directorio existe
285
350
  const dir = path.dirname(rutaDestino);
@@ -0,0 +1,94 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ /**
5
+ * Cargador de variables de entorno propio de DBCube.
6
+ *
7
+ * Evita que el usuario tenga que instalar `dotenv` y escribir
8
+ * `require("dotenv").config()` al inicio de su dbcube.config.js. DBCube carga
9
+ * el `.env` del proyecto automáticamente ANTES de evaluar la configuración.
10
+ *
11
+ * Estrategia:
12
+ * 1. `process.loadEnvFile()` — nativo de Node 20.6+ (cero dependencias).
13
+ * 2. Fallback: parser propio mínimo (KEY=VALUE, comillas, comentarios, export).
14
+ *
15
+ * No sobrescribe variables ya presentes en process.env (las del shell mandan).
16
+ */
17
+ class EnvLoader {
18
+ /**
19
+ * Carga el archivo .env indicado (o ./.env del cwd) en process.env.
20
+ * Silencioso si el archivo no existe. Devuelve true si cargó algo.
21
+ * @param {string} [envPath]
22
+ * @returns {boolean}
23
+ */
24
+ static load(envPath) {
25
+ const file = envPath || path.resolve(process.cwd(), '.env');
26
+ if (!fs.existsSync(file)) return false;
27
+
28
+ // Camino preferido: cargador nativo de Node (20.6+).
29
+ if (typeof process.loadEnvFile === 'function') {
30
+ try {
31
+ process.loadEnvFile(file);
32
+ return true;
33
+ } catch {
34
+ // Cae al parser propio si el nativo falla por algún motivo.
35
+ }
36
+ }
37
+
38
+ try {
39
+ const content = fs.readFileSync(file, 'utf8');
40
+ const parsed = EnvLoader.parse(content);
41
+ for (const [key, value] of Object.entries(parsed)) {
42
+ if (process.env[key] === undefined) {
43
+ process.env[key] = value;
44
+ }
45
+ }
46
+ return true;
47
+ } catch {
48
+ return false;
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Parser mínimo de formato .env. Soporta:
54
+ * KEY=value · KEY="value con espacios" · KEY='literal' · export KEY=value
55
+ * comentarios con # · líneas en blanco · \n \t en comillas dobles.
56
+ * @param {string} content
57
+ * @returns {Record<string,string>}
58
+ */
59
+ static parse(content) {
60
+ const out = {};
61
+ for (let rawLine of content.split(/\r?\n/)) {
62
+ const line = rawLine.trim();
63
+ if (!line || line.startsWith('#')) continue;
64
+
65
+ // Permite el prefijo `export` (estilo shell).
66
+ const withoutExport = line.startsWith('export ') ? line.slice(7).trim() : line;
67
+
68
+ const eq = withoutExport.indexOf('=');
69
+ if (eq === -1) continue;
70
+
71
+ const key = withoutExport.slice(0, eq).trim();
72
+ if (!key) continue;
73
+
74
+ let value = withoutExport.slice(eq + 1).trim();
75
+
76
+ // Comillas dobles: interpreta escapes. Comillas simples: literal.
77
+ if (value.length >= 2 && value[0] === '"' && value[value.length - 1] === '"') {
78
+ value = value.slice(1, -1).replace(/\\n/g, '\n').replace(/\\t/g, '\t').replace(/\\"/g, '"');
79
+ } else if (value.length >= 2 && value[0] === "'" && value[value.length - 1] === "'") {
80
+ value = value.slice(1, -1);
81
+ } else {
82
+ // Sin comillas: corta un comentario en línea (espacio + #).
83
+ const hash = value.indexOf(' #');
84
+ if (hash !== -1) value = value.slice(0, hash).trim();
85
+ }
86
+
87
+ out[key] = value;
88
+ }
89
+ return out;
90
+ }
91
+ }
92
+
93
+ module.exports = EnvLoader;
94
+ module.exports.EnvLoader = EnvLoader;