@orxataguy/tyr 1.0.7 → 1.0.9

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": "@orxataguy/tyr",
3
- "version": "1.0.7",
3
+ "version": "1.0.9",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "tyr": "./bin/tyr.js"
@@ -49,6 +49,7 @@
49
49
  "find-config": "^1.0.0",
50
50
  "inquirer": "^13.2.1",
51
51
  "js-yaml": "^4.1.1",
52
+ "mongodb": "^7.2.0",
52
53
  "mssql": "^12.2.0",
53
54
  "tsx": "^4.21.0"
54
55
  },
@@ -11,6 +11,7 @@ import rem from './sys/rem';
11
11
  import doc from './sys/doc';
12
12
  import ai from './sys/ai';
13
13
  import config from './sys/config';
14
+ import help from './sys/help';
14
15
 
15
16
  import { TyrError } from './TyrError';
16
17
 
@@ -126,6 +127,20 @@ export class Kernel {
126
127
  return;
127
128
  }
128
129
 
130
+ // --help / -h: lista todos los comandos disponibles con su documentación
131
+ if (commandName === '--help' || commandName === '-h') {
132
+ const helpContext = {
133
+ ...this.container.get(),
134
+ frameworkRoot: this.frameworkRoot,
135
+ userRoot: this.userRoot,
136
+ run: async () => {},
137
+ task: async <T>(_: string, action: () => Promise<T> | T) => action(),
138
+ fail: (msg: string) => { throw new Error(msg); },
139
+ } as any;
140
+ await help(helpContext)(args.slice(1));
141
+ return;
142
+ }
143
+
129
144
  const runInternal = async (cmd: string, cmdArgs: string[] = []) => {
130
145
  await this.handle([cmd, ...cmdArgs]);
131
146
  };
@@ -0,0 +1,149 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { TyrContext } from '../Kernel';
4
+
5
+ interface CommandDoc {
6
+ name: string;
7
+ description: string;
8
+ usage: string;
9
+ }
10
+
11
+ /**
12
+ * Extrae el primer bloque JSDoc de un archivo .tyr.ts y lo parsea
13
+ * en descripción y ejemplos de uso.
14
+ */
15
+ function parseCommandDoc(filePath: string): CommandDoc {
16
+ const fileName = path.basename(filePath, '.tyr.ts');
17
+ const content = fs.readFileSync(filePath, 'utf-8');
18
+
19
+ const match = content.match(/\/\*\*([\s\S]*?)\*\//);
20
+ if (!match) {
21
+ return { name: fileName, description: '', usage: '' };
22
+ }
23
+
24
+ // Limpiar cada línea: eliminar el * inicial y espacios
25
+ const lines = match[1]
26
+ .split('\n')
27
+ .map(line => line.replace(/^\s*\*\s?/, '').trimEnd());
28
+
29
+ // Separar en descripción y bloque "Uso:"
30
+ const usoIndex = lines.findIndex(l => /^uso:/i.test(l.trim()));
31
+
32
+ let description = '';
33
+ let usage = '';
34
+
35
+ if (usoIndex !== -1) {
36
+ description = lines
37
+ .slice(0, usoIndex)
38
+ .filter(l => l.trim() !== '')
39
+ .join('\n')
40
+ .trim();
41
+
42
+ usage = lines
43
+ .slice(usoIndex + 1)
44
+ .filter(l => l.trim() !== '')
45
+ .map(l => l.trim())
46
+ .join('\n')
47
+ .trim();
48
+ } else {
49
+ description = lines.filter(l => l.trim() !== '').join('\n').trim();
50
+ }
51
+
52
+ return { name: fileName, description, usage };
53
+ }
54
+
55
+ export default function help({ userRoot }: TyrContext) {
56
+ return async (_args: string[]) => {
57
+ const commandsDir = path.join(userRoot, 'commands');
58
+
59
+ // ── ANSI ──────────────────────────────────────────────────────────
60
+ const reset = '\x1b[0m';
61
+ const bold = '\x1b[1m';
62
+ const dim = '\x1b[2m';
63
+ const cyan = '\x1b[36m';
64
+ const green = '\x1b[32m';
65
+ const yellow = '\x1b[33m';
66
+ const gray = '\x1b[90m';
67
+ const white = '\x1b[37m';
68
+ // ──────────────────────────────────────────────────────────────────
69
+
70
+ const separator = `${gray} ${'─'.repeat(50)}${reset}`;
71
+
72
+ console.log('');
73
+ console.log(` ${bold}${cyan}tyr${reset} ${white}Comandos disponibles${reset}`);
74
+ console.log(separator);
75
+ console.log('');
76
+
77
+ // Flags y comandos built-in del framework
78
+ const builtins = [
79
+ { name: '--help', description: 'Muestra este listado de comandos.', usage: 'tyr --help' },
80
+ { name: '--version', description: 'Muestra la versión instalada de tyr.', usage: 'tyr --version' },
81
+ { name: '--config', description: 'Configura tyr por primera vez.', usage: 'tyr --config' },
82
+ { name: '--update', description: 'Actualiza ~/.tyr desde el repositorio git.', usage: 'tyr --update' },
83
+ { name: '--upgrade', description: 'Actualiza el paquete npm de tyr.', usage: 'tyr --upgrade' },
84
+ { name: 'gen', description: 'Genera un nuevo comando a partir de una descripción con IA.', usage: 'tyr gen <nombre> "<descripción>"' },
85
+ { name: 'doc', description: 'Levanta la documentación del framework en el navegador.', usage: 'tyr doc' },
86
+ ];
87
+
88
+ console.log(` ${bold}${yellow}Framework${reset}`);
89
+ console.log('');
90
+
91
+ for (const cmd of builtins) {
92
+ console.log(` ${bold}${green}${cmd.name.padEnd(14)}${reset}${dim}${cmd.description}${reset}`);
93
+ console.log(` ${' '.repeat(14)}${gray}${cmd.usage}${reset}`);
94
+ console.log('');
95
+ }
96
+
97
+ // Comandos de usuario en ~/.tyr/commands/
98
+ if (!fs.existsSync(commandsDir)) {
99
+ console.log(separator);
100
+ console.log(` ${yellow}No se encontró la carpeta de comandos: ${commandsDir}${reset}`);
101
+ console.log('');
102
+ return;
103
+ }
104
+
105
+ const files = fs.readdirSync(commandsDir)
106
+ .filter(f => f.endsWith('.tyr.ts'))
107
+ .sort();
108
+
109
+ if (files.length === 0) {
110
+ console.log(separator);
111
+ console.log(` ${dim}No hay comandos en ${commandsDir}${reset}`);
112
+ console.log('');
113
+ return;
114
+ }
115
+
116
+ console.log(separator);
117
+ console.log('');
118
+ console.log(` ${bold}${yellow}Comandos de usuario${reset} ${gray}(~/.tyr/commands/)${reset}`);
119
+ console.log('');
120
+
121
+ for (const file of files) {
122
+ const doc = parseCommandDoc(path.join(commandsDir, file));
123
+
124
+ console.log(` ${bold}${green}${doc.name}${reset}`);
125
+
126
+ if (doc.description) {
127
+ for (const line of doc.description.split('\n')) {
128
+ console.log(` ${dim}${line}${reset}`);
129
+ }
130
+ } else {
131
+ console.log(` ${gray}Sin descripción${reset}`);
132
+ }
133
+
134
+ if (doc.usage) {
135
+ console.log('');
136
+ console.log(` ${gray} Uso:${reset}`);
137
+ for (const line of doc.usage.split('\n')) {
138
+ console.log(` ${cyan} ${line}${reset}`);
139
+ }
140
+ }
141
+
142
+ console.log('');
143
+ }
144
+
145
+ console.log(separator);
146
+ console.log(` ${dim}Genera un comando nuevo con ${cyan}tyr gen <nombre> "<qué debe hacer>"${reset}`);
147
+ console.log('');
148
+ };
149
+ }
@@ -0,0 +1,176 @@
1
+ import { MongoClient, Db, Document, Filter, UpdateFilter, InsertOneResult, InsertManyResult, UpdateResult, DeleteResult, WithId, OptionalUnlessRequiredId, FindOptions } from 'mongodb';
2
+
3
+ /**
4
+ * @class MongoManager
5
+ * @description Conector con MongoDB que gestiona el ciclo de vida de la conexión y expone un CRUD genérico.
6
+ */
7
+ export class MongoManager {
8
+ private client!: MongoClient;
9
+ private db!: Db;
10
+ private connected = false;
11
+
12
+ constructor() {}
13
+
14
+ private async init(): Promise<void> {
15
+ if (!this.connected) {
16
+ const uri = process.env.MONGO_URI || 'mongodb://localhost:27017';
17
+ const dbName = process.env.MONGO_DATABASE || '';
18
+
19
+ this.client = new MongoClient(uri);
20
+ await this.client.connect();
21
+ this.db = this.client.db(dbName);
22
+ this.connected = true;
23
+ }
24
+ }
25
+
26
+ private async close(): Promise<void> {
27
+ if (this.connected && this.client) {
28
+ await this.client.close();
29
+ this.connected = false;
30
+ }
31
+ }
32
+
33
+ /**
34
+ * @method insertOne
35
+ * @description Inserta un documento en la colección indicada.
36
+ * @param {string} collection - Nombre de la colección.
37
+ * @param {Document} document - Documento a insertar.
38
+ * @returns {Promise<InsertOneResult>} Resultado de la inserción.
39
+ * @example
40
+ * const result = await mongo.insertOne('users', { name: 'Ana', age: 30 });
41
+ */
42
+ public async insertOne<T extends Document>(collection: string, document: OptionalUnlessRequiredId<T>): Promise<InsertOneResult<T>> {
43
+ await this.init();
44
+ const result = await this.db.collection<T>(collection).insertOne(document);
45
+ await this.close();
46
+ return result;
47
+ }
48
+
49
+ /**
50
+ * @method insertMany
51
+ * @description Inserta múltiples documentos en la colección indicada.
52
+ * @param {string} collection - Nombre de la colección.
53
+ * @param {Document[]} documents - Array de documentos a insertar.
54
+ * @returns {Promise<InsertManyResult>} Resultado de la inserción.
55
+ * @example
56
+ * const result = await mongo.insertMany('users', [{ name: 'Ana' }, { name: 'Luis' }]);
57
+ */
58
+ public async insertMany<T extends Document>(collection: string, documents: OptionalUnlessRequiredId<T>[]): Promise<InsertManyResult<T>> {
59
+ await this.init();
60
+ const result = await this.db.collection<T>(collection).insertMany(documents);
61
+ await this.close();
62
+ return result;
63
+ }
64
+
65
+ /**
66
+ * @method findOne
67
+ * @description Busca el primer documento que coincida con el filtro.
68
+ * @param {string} collection - Nombre de la colección.
69
+ * @param {Filter<Document>} filter - Filtro de búsqueda.
70
+ * @param {FindOptions} [options] - Opciones adicionales (proyección, etc.).
71
+ * @returns {Promise<WithId<T> | null>} El documento encontrado o null.
72
+ * @example
73
+ * const user = await mongo.findOne('users', { name: 'Ana' });
74
+ */
75
+ public async findOne<T extends Document>(collection: string, filter: Filter<T>, options?: FindOptions): Promise<WithId<T> | null> {
76
+ await this.init();
77
+ const result = await this.db.collection<T>(collection).findOne(filter, options);
78
+ await this.close();
79
+ return result;
80
+ }
81
+
82
+ /**
83
+ * @method find
84
+ * @description Busca todos los documentos que coincidan con el filtro.
85
+ * @param {string} collection - Nombre de la colección.
86
+ * @param {Filter<Document>} filter - Filtro de búsqueda. Usa {} para traer todos los documentos.
87
+ * @param {FindOptions} [options] - Opciones adicionales (proyección, límite, etc.).
88
+ * @returns {Promise<WithId<T>[]>} Array de documentos encontrados.
89
+ * @example
90
+ * const users = await mongo.find('users', { age: { $gte: 18 } });
91
+ */
92
+ public async find<T extends Document>(collection: string, filter: Filter<T>, options?: FindOptions): Promise<WithId<T>[]> {
93
+ await this.init();
94
+ const result = await this.db.collection<T>(collection).find(filter, options).toArray();
95
+ await this.close();
96
+ return result;
97
+ }
98
+
99
+ /**
100
+ * @method updateOne
101
+ * @description Actualiza el primer documento que coincida con el filtro.
102
+ * @param {string} collection - Nombre de la colección.
103
+ * @param {Filter<Document>} filter - Filtro para identificar el documento.
104
+ * @param {UpdateFilter<Document>} update - Operación de actualización (ej: { $set: { field: value } }).
105
+ * @returns {Promise<UpdateResult>} Resultado de la actualización.
106
+ * @example
107
+ * const result = await mongo.updateOne('users', { name: 'Ana' }, { $set: { age: 31 } });
108
+ */
109
+ public async updateOne<T extends Document>(collection: string, filter: Filter<T>, update: UpdateFilter<T>): Promise<UpdateResult<T>> {
110
+ await this.init();
111
+ const result = await this.db.collection<T>(collection).updateOne(filter, update);
112
+ await this.close();
113
+ return result;
114
+ }
115
+
116
+ /**
117
+ * @method updateMany
118
+ * @description Actualiza todos los documentos que coincidan con el filtro.
119
+ * @param {string} collection - Nombre de la colección.
120
+ * @param {Filter<Document>} filter - Filtro para identificar los documentos.
121
+ * @param {UpdateFilter<Document>} update - Operación de actualización.
122
+ * @returns {Promise<UpdateResult>} Resultado de la actualización.
123
+ * @example
124
+ * const result = await mongo.updateMany('users', { active: false }, { $set: { active: true } });
125
+ */
126
+ public async updateMany<T extends Document>(collection: string, filter: Filter<T>, update: UpdateFilter<T>): Promise<UpdateResult<T>> {
127
+ await this.init();
128
+ const result = await this.db.collection<T>(collection).updateMany(filter, update);
129
+ await this.close();
130
+ return result;
131
+ }
132
+
133
+ /**
134
+ * @method deleteOne
135
+ * @description Elimina el primer documento que coincida con el filtro.
136
+ * @param {string} collection - Nombre de la colección.
137
+ * @param {Filter<Document>} filter - Filtro para identificar el documento.
138
+ * @returns {Promise<DeleteResult>} Resultado de la eliminación.
139
+ * @example
140
+ * const result = await mongo.deleteOne('users', { name: 'Ana' });
141
+ */
142
+ public async deleteOne<T extends Document>(collection: string, filter: Filter<T>): Promise<DeleteResult> {
143
+ await this.init();
144
+ const result = await this.db.collection<T>(collection).deleteOne(filter);
145
+ await this.close();
146
+ return result;
147
+ }
148
+
149
+ /**
150
+ * @method deleteMany
151
+ * @description Elimina todos los documentos que coincidan con el filtro.
152
+ * @param {string} collection - Nombre de la colección.
153
+ * @param {Filter<Document>} filter - Filtro para identificar los documentos.
154
+ * @returns {Promise<DeleteResult>} Resultado de la eliminación.
155
+ * @example
156
+ * const result = await mongo.deleteMany('users', { active: false });
157
+ */
158
+ public async deleteMany<T extends Document>(collection: string, filter: Filter<T>): Promise<DeleteResult> {
159
+ await this.init();
160
+ const result = await this.db.collection<T>(collection).deleteMany(filter);
161
+ await this.close();
162
+ return result;
163
+ }
164
+ }
165
+
166
+ /**
167
+ * @object MongoManagerTests
168
+ * @description Parámetros de pruebas para validar la funcionalidad de MongoManager.
169
+ */
170
+ export const MongoManagerTests = {
171
+ // insertOne: { collection: 'test', document: { name: 'test_doc', value: 1 } },
172
+ // findOne: { collection: 'test', filter: { name: 'test_doc' } },
173
+ // find: { collection: 'test', filter: {} },
174
+ // updateOne: { collection: 'test', filter: { name: 'test_doc' }, update: { $set: { value: 2 } } },
175
+ // deleteOne: { collection: 'test', filter: { name: 'test_doc' } },
176
+ };