@orxataguy/tyr 1.0.0 → 1.0.2
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/LICENSE +21 -21
- package/README.md +408 -408
- package/bin/tyr.js +10 -7
- package/bin/tyr.ts +13 -13
- package/config/map.yml +4 -7
- package/package.json +66 -60
- package/src/commands/di.tyr.ts +112 -112
- package/src/commands/dw.tyr.ts +115 -115
- package/src/commands/install.tyr.ts +30 -104
- package/src/core/Container.ts +56 -56
- package/src/core/Kernel.ts +213 -165
- package/src/core/Logger.ts +51 -48
- package/src/core/TyrError.ts +57 -57
- package/src/core/sys/ai.ts +160 -162
- package/src/core/sys/config.ts +231 -0
- package/src/core/sys/doc.ts +324 -324
- package/src/core/sys/gen.ts +75 -72
- package/src/core/sys/rem.ts +61 -57
- package/src/index.ts +5 -0
- package/src/lib/DockerManager.ts +108 -108
- package/src/lib/FileSystemManager.ts +152 -152
- package/src/lib/GitManager.ts +75 -75
- package/src/lib/PackageManager.ts +87 -87
- package/src/lib/SQLManager.ts +112 -120
- package/src/lib/ShellManager.ts +117 -117
- package/src/lib/SystemManager.ts +83 -83
- package/src/lib/WebManager.ts +62 -62
package/src/core/sys/doc.ts
CHANGED
|
@@ -1,325 +1,325 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import http from 'http';
|
|
4
|
-
import { TyrContext } from '../Kernel';
|
|
5
|
-
|
|
6
|
-
interface DocMethod {
|
|
7
|
-
name: string;
|
|
8
|
-
description: string;
|
|
9
|
-
example: string | null;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
interface DocStructure {
|
|
13
|
-
name: string;
|
|
14
|
-
description: string;
|
|
15
|
-
methods: DocMethod[];
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export default function doc({ logger, frameworkRoot, run }: TyrContext) {
|
|
19
|
-
return async (args: string[]) => {
|
|
20
|
-
logger.info("📚 Generando documentación del sistema (TS Mode)...");
|
|
21
|
-
|
|
22
|
-
const libPath = path.resolve(frameworkRoot, 'src/lib');
|
|
23
|
-
|
|
24
|
-
const parseJSDoc = (filename: string, content: string): DocStructure => {
|
|
25
|
-
const fileDoc: DocStructure = {
|
|
26
|
-
name: filename,
|
|
27
|
-
description: "Sin descripción.",
|
|
28
|
-
methods: []
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
const cleanJSDoc = (raw: string) => {
|
|
32
|
-
return raw
|
|
33
|
-
.split('\n')
|
|
34
|
-
.map(line => {
|
|
35
|
-
return line.trim().replace(/^\*+\s?/, '');
|
|
36
|
-
})
|
|
37
|
-
.filter(line => line !== '')
|
|
38
|
-
.join('\n');
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
const commentRegex = /\/\*\*([\s\S]*?)\*\//g;
|
|
42
|
-
let match;
|
|
43
|
-
|
|
44
|
-
while ((match = commentRegex.exec(content)) !== null) {
|
|
45
|
-
const rawComment = match[1];
|
|
46
|
-
const cleanComment = cleanJSDoc(rawComment);
|
|
47
|
-
|
|
48
|
-
const nextCodeIndex = commentRegex.lastIndex;
|
|
49
|
-
const codeSnippet = content.substring(nextCodeIndex, nextCodeIndex + 200);
|
|
50
|
-
|
|
51
|
-
const isClass = /@class/.test(cleanComment) || /^\s*export\s+class/.test(codeSnippet);
|
|
52
|
-
|
|
53
|
-
if (isClass) {
|
|
54
|
-
const descMatch = cleanComment.match(/@description\s+([\s\S]*?)(?=@|$)/i);
|
|
55
|
-
if (descMatch) {
|
|
56
|
-
fileDoc.description = descMatch[1].trim();
|
|
57
|
-
} else {
|
|
58
|
-
fileDoc.description = cleanComment.split('\n')[0];
|
|
59
|
-
}
|
|
60
|
-
const classNameMatch = codeSnippet.match(/class\s+(\w+)/);
|
|
61
|
-
if (classNameMatch) fileDoc.name = classNameMatch[1];
|
|
62
|
-
continue;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
let methodName = null;
|
|
66
|
-
|
|
67
|
-
const methodTagMatch = cleanComment.match(/@method\s+(\w+)/i);
|
|
68
|
-
if (methodTagMatch) {
|
|
69
|
-
methodName = methodTagMatch[1];
|
|
70
|
-
} else if (!codeSnippet.match(/^\s*constructor/)) {
|
|
71
|
-
const codeMatch = codeSnippet.match(/(?:public|private|protected)\s+(?:async\s+)?(\w+)/);
|
|
72
|
-
if (codeMatch) {
|
|
73
|
-
methodName = codeMatch[1];
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
if (methodName) {
|
|
78
|
-
let description = "";
|
|
79
|
-
const descMatch = cleanComment.match(/@description\s+([\s\S]*?)(?=@|$)/i);
|
|
80
|
-
if (descMatch) {
|
|
81
|
-
description = descMatch[1].trim();
|
|
82
|
-
} else {
|
|
83
|
-
const textLines = cleanComment.split('\n').filter(l => !l.startsWith('@'));
|
|
84
|
-
description = textLines.join(' ').trim() || "Sin descripción";
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
let example = null;
|
|
88
|
-
const exampleMatch = cleanComment.match(/@example([\s\S]*?)(?=@|$)/i);
|
|
89
|
-
if (exampleMatch) {
|
|
90
|
-
example = exampleMatch[1]
|
|
91
|
-
.replace(/```ts|```/g, '')
|
|
92
|
-
.trim();
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
fileDoc.methods.push({
|
|
96
|
-
name: methodName,
|
|
97
|
-
description: description,
|
|
98
|
-
example: example
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
return fileDoc;
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
if (!fs.existsSync(libPath)) {
|
|
107
|
-
logger.error(`No se encuentra la carpeta de librerías: ${libPath}`);
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
const files = fs.readdirSync(libPath).filter(f => f.endsWith('.ts'));
|
|
112
|
-
|
|
113
|
-
if (files.length === 0) {
|
|
114
|
-
logger.warn("No se encontraron archivos .ts en /src/lib para documentar.");
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const fileDocs = files.map(file => {
|
|
118
|
-
return parseJSDoc(file, fs.readFileSync(path.join(libPath, file), 'utf8'));
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
const systemDocs: DocStructure = {
|
|
122
|
-
name: 'TyrContext (Kernel)',
|
|
123
|
-
description: 'Utilidades globales inyectadas en cada comando. Accesibles destructurando el contexto.',
|
|
124
|
-
methods: [
|
|
125
|
-
{
|
|
126
|
-
name: 'run',
|
|
127
|
-
description: 'Ejecuta otro comando del sistema programáticamente (Composición de comandos). Útil para que un comando invoque a otros.',
|
|
128
|
-
example: `
|
|
129
|
-
// Llama al comando 'test' pasándole argumentos adicionales
|
|
130
|
-
const secret = "123";
|
|
131
|
-
args.push(secret);
|
|
132
|
-
await run('test', args);`.trim()
|
|
133
|
-
},
|
|
134
|
-
{
|
|
135
|
-
name: 'task',
|
|
136
|
-
description: 'Helper que envuelve una operación crítica. Si falla, el framework captura el error, añade contexto y lo muestra limpio en consola. Elimina la necesidad de try/catch manuales.',
|
|
137
|
-
example: `
|
|
138
|
-
// Ejemplo: Tarea asíncrona que retorna un valor
|
|
139
|
-
const buildId = await task('Compilando proyecto', async () => {
|
|
140
|
-
return await shell.exec('npm run build');
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
// Si falla, el log dirá: "Falló la tarea: Compilando proyecto"`.trim()
|
|
144
|
-
},
|
|
145
|
-
{
|
|
146
|
-
name: 'fail',
|
|
147
|
-
description: 'Detiene la ejecución del comando inmediatamente lanzando un error controlado. Permite añadir una "sugerencia" para ayudar al usuario a solucionarlo.',
|
|
148
|
-
example: `
|
|
149
|
-
// Úsalo para validaciones lógicas
|
|
150
|
-
if (!fs.existsSync('./package.json')) {
|
|
151
|
-
fail(
|
|
152
|
-
'No se encuentra el archivo package de npm',
|
|
153
|
-
'Ejecuta "npm init -y" para generar uno.'
|
|
154
|
-
);
|
|
155
|
-
}`.trim()
|
|
156
|
-
},
|
|
157
|
-
{
|
|
158
|
-
name: 'logger',
|
|
159
|
-
description: 'Sistema de logs estandarizado con colores y formatos.',
|
|
160
|
-
example: `logger.info('Iniciando...');\nlogger.success('Creado');\nlogger.warn('Cuidado');`
|
|
161
|
-
}
|
|
162
|
-
]
|
|
163
|
-
};
|
|
164
|
-
|
|
165
|
-
const docs = [systemDocs, ...fileDocs];
|
|
166
|
-
|
|
167
|
-
const html = `
|
|
168
|
-
<!DOCTYPE html>
|
|
169
|
-
<html>
|
|
170
|
-
<head>
|
|
171
|
-
<meta charset="utf-8">
|
|
172
|
-
<title>Tyr Docs</title>
|
|
173
|
-
<style>
|
|
174
|
-
body { font-family: 'Segoe UI', sans-serif; background: #222; color: #eee; padding: 20px; display: flex; margin: 0; }
|
|
175
|
-
nav { width: 220px; border-right: 1px solid #444; margin-right: 20px; padding-right: 20px; height: 100vh; overflow-y: auto; position: sticky; top: 0; }
|
|
176
|
-
a { color: #4db8ff; text-decoration: none; display: block; margin: 8px 0; padding: 5px; border-radius: 4px; transition: 0.2s; }
|
|
177
|
-
a:hover { background: #333; }
|
|
178
|
-
main { flex: 1; overflow-y: auto; }
|
|
179
|
-
.card { background: #2d2d2d; padding: 20px; margin-bottom: 30px; border-radius: 8px; border: 1px solid #333; }
|
|
180
|
-
h2 { border-bottom: 1px solid #444; padding-bottom: 10px; margin-top: 0; color: #fff; }
|
|
181
|
-
.method { margin-top: 25px; padding-left: 15px; border-left: 3px solid #4db8ff; }
|
|
182
|
-
h3 { margin: 0 0 5px 0; color: #4db8ff; font-family: monospace; font-size: 1.2em; }
|
|
183
|
-
.desc { color: #ccc; margin-bottom: 10px; }
|
|
184
|
-
pre { background: #1a1a1a; padding: 15px; border-radius: 5px; overflow-x: auto; border: 1px solid #444; color: #ce9178; font-family: monospace; white-space: pre-wrap; }
|
|
185
|
-
.tag-ts { background: #007acc; color: white; padding: 2px 6px; border-radius: 3px; font-size: 0.7em; margin-left: 10px; vertical-align: middle; }
|
|
186
|
-
.prompt-box { background: #1a1a1a; border: 2px solid #4db8ff; padding: 25px; border-radius: 8px; margin-top: 40px; position: relative; }
|
|
187
|
-
.prompt-box h2 { color: #4db8ff; margin-top: 0; border: none; }
|
|
188
|
-
.copy-btn { position: absolute; top: 20px; right: 20px; background: #4db8ff; color: #000; border: none; padding: 8px 16px; border-radius: 5px; cursor: pointer; font-weight: bold; transition: 0.2s; }
|
|
189
|
-
.copy-btn:hover { background: #6dc9ff; }
|
|
190
|
-
.copy-btn:active { background: #2da3e0; }
|
|
191
|
-
</style>
|
|
192
|
-
</head>
|
|
193
|
-
<body>
|
|
194
|
-
<nav>
|
|
195
|
-
<h3 style="color: #888; text-transform: uppercase; font-size: 0.8rem;">Módulos TS</h3>
|
|
196
|
-
${docs.map(d => `<a href="#${d.name}">📦 ${d.name.replace('.ts', '')}</a>`).join('')}
|
|
197
|
-
<a href="#ai-generator" style="margin-top: 20px; background: #4db8ff; color: #000; font-weight: bold;">🤖 Generador IA</a>
|
|
198
|
-
</nav>
|
|
199
|
-
<main>
|
|
200
|
-
${docs.map(d => `
|
|
201
|
-
<div id="${d.name}" class="card">
|
|
202
|
-
<h2>${d.name} <span class="tag-ts">TS</span></h2>
|
|
203
|
-
<p style="font-size: 1.1em; color: #bbb;">${d.description}</p>
|
|
204
|
-
${d.methods.map(m => `
|
|
205
|
-
<div class="method">
|
|
206
|
-
<h3>${m.name}()</h3>
|
|
207
|
-
<p class="desc">${m.description}</p>
|
|
208
|
-
${m.example ? `<pre>${m.example}</pre>` : ''}
|
|
209
|
-
</div>
|
|
210
|
-
`).join('')}
|
|
211
|
-
</div>
|
|
212
|
-
`).join('')}
|
|
213
|
-
|
|
214
|
-
<div id="ai-generator" class="prompt-box">
|
|
215
|
-
<h2>🤖 Generador de Comandos con IA</h2>
|
|
216
|
-
<p style="color: #bbb; margin-bottom: 20px;">
|
|
217
|
-
Describe qué debe hacer tu comando y la IA lo generará automáticamente usando la documentación del framework.
|
|
218
|
-
</p>
|
|
219
|
-
<div style="margin-bottom: 15px;">
|
|
220
|
-
<label style="display: block; color: #4db8ff; margin-bottom: 5px; font-weight: bold;">Nombre del comando</label>
|
|
221
|
-
<input type="text" id="cmd-name" placeholder="mi-comando"
|
|
222
|
-
style="width: 100%; padding: 10px; background: #2d2d2d; border: 1px solid #444; color: #eee; border-radius: 5px; font-size: 1em; box-sizing: border-box;" />
|
|
223
|
-
</div>
|
|
224
|
-
<div style="margin-bottom: 15px;">
|
|
225
|
-
<label style="display: block; color: #4db8ff; margin-bottom: 5px; font-weight: bold;">Describe qué debe hacer el comando</label>
|
|
226
|
-
<textarea id="cmd-prompt" rows="6" placeholder="Ej: Crea un comando que liste todos los contenedores Docker activos y muestre su estado en una tabla formateada..."
|
|
227
|
-
style="width: 100%; padding: 10px; background: #2d2d2d; border: 1px solid #444; color: #eee; border-radius: 5px; font-size: 1em; resize: vertical; box-sizing: border-box;"></textarea>
|
|
228
|
-
</div>
|
|
229
|
-
<button id="generate-btn" onclick="generateCommand()"
|
|
230
|
-
style="background: #4db8ff; color: #000; border: none; padding: 12px 24px; border-radius: 5px; cursor: pointer; font-weight: bold; font-size: 1em; transition: 0.2s; width: 100%;">
|
|
231
|
-
Generar Comando
|
|
232
|
-
</button>
|
|
233
|
-
<div id="gen-status" style="margin-top: 15px; display: none; padding: 15px; border-radius: 5px;"></div>
|
|
234
|
-
</div>
|
|
235
|
-
</main>
|
|
236
|
-
<script>
|
|
237
|
-
async function generateCommand() {
|
|
238
|
-
const name = document.getElementById('cmd-name').value.trim();
|
|
239
|
-
const prompt = document.getElementById('cmd-prompt').value.trim();
|
|
240
|
-
const btn = document.getElementById('generate-btn');
|
|
241
|
-
const status = document.getElementById('gen-status');
|
|
242
|
-
|
|
243
|
-
if (!name || !prompt) {
|
|
244
|
-
status.style.display = 'block';
|
|
245
|
-
status.style.background = '#4a2020';
|
|
246
|
-
status.style.border = '1px solid #ff4444';
|
|
247
|
-
status.textContent = 'Rellena ambos campos.';
|
|
248
|
-
return;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
btn.disabled = true;
|
|
252
|
-
btn.textContent = 'Generando...';
|
|
253
|
-
btn.style.background = '#888';
|
|
254
|
-
status.style.display = 'block';
|
|
255
|
-
status.style.background = '#1a1a2e';
|
|
256
|
-
status.style.border = '1px solid #4db8ff';
|
|
257
|
-
status.textContent = 'Enviando prompt a la IA... Esto puede tardar unos segundos.';
|
|
258
|
-
|
|
259
|
-
try {
|
|
260
|
-
const res = await fetch('/generate', {
|
|
261
|
-
method: 'POST',
|
|
262
|
-
headers: { 'Content-Type': 'application/json' },
|
|
263
|
-
body: JSON.stringify({ name, prompt })
|
|
264
|
-
});
|
|
265
|
-
const data = await res.json();
|
|
266
|
-
|
|
267
|
-
if (data.success) {
|
|
268
|
-
status.style.background = '#1a2e1a';
|
|
269
|
-
status.style.border = '1px solid #4ade80';
|
|
270
|
-
status.textContent = data.message;
|
|
271
|
-
} else {
|
|
272
|
-
status.style.background = '#4a2020';
|
|
273
|
-
status.style.border = '1px solid #ff4444';
|
|
274
|
-
status.textContent = data.message;
|
|
275
|
-
}
|
|
276
|
-
} catch (e) {
|
|
277
|
-
status.style.background = '#4a2020';
|
|
278
|
-
status.style.border = '1px solid #ff4444';
|
|
279
|
-
status.textContent = 'Error de conexión con el servidor.';
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
btn.disabled = false;
|
|
283
|
-
btn.textContent = 'Generar Comando';
|
|
284
|
-
btn.style.background = '#4db8ff';
|
|
285
|
-
}
|
|
286
|
-
</script>
|
|
287
|
-
</body>
|
|
288
|
-
</html>`;
|
|
289
|
-
|
|
290
|
-
const PORT = 3000;
|
|
291
|
-
const server = http.createServer(async (req, res) => {
|
|
292
|
-
if (req.method === 'POST' && req.url === '/generate') {
|
|
293
|
-
let body = '';
|
|
294
|
-
req.on('data', (chunk: Buffer) => { body += chunk.toString(); });
|
|
295
|
-
req.on('end', async () => {
|
|
296
|
-
try {
|
|
297
|
-
const { name, prompt } = JSON.parse(body);
|
|
298
|
-
if (!name || !prompt) {
|
|
299
|
-
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
300
|
-
res.end(JSON.stringify({ success: false, message: 'Faltan campos obligatorios.' }));
|
|
301
|
-
return;
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
await run('ai', [name, prompt]);
|
|
305
|
-
|
|
306
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
307
|
-
res.end(JSON.stringify({ success: true, message: `Comando '${name}' generado correctamente en src/commands/${name}.tyr.ts` }));
|
|
308
|
-
} catch (e: any) {
|
|
309
|
-
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
310
|
-
res.end(JSON.stringify({ success: false, message: e.message || 'Error al generar el comando.' }));
|
|
311
|
-
}
|
|
312
|
-
});
|
|
313
|
-
return;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
317
|
-
res.end(html);
|
|
318
|
-
});
|
|
319
|
-
|
|
320
|
-
server.listen(PORT, () => {
|
|
321
|
-
logger.success(`Documentación TS lista en: http://localhost:${PORT}`);
|
|
322
|
-
logger.info("Presiona Ctrl+C para detener.");
|
|
323
|
-
});
|
|
324
|
-
};
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import http from 'http';
|
|
4
|
+
import { TyrContext } from '../Kernel';
|
|
5
|
+
|
|
6
|
+
interface DocMethod {
|
|
7
|
+
name: string;
|
|
8
|
+
description: string;
|
|
9
|
+
example: string | null;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface DocStructure {
|
|
13
|
+
name: string;
|
|
14
|
+
description: string;
|
|
15
|
+
methods: DocMethod[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export default function doc({ logger, frameworkRoot, run }: TyrContext) {
|
|
19
|
+
return async (args: string[]) => {
|
|
20
|
+
logger.info("📚 Generando documentación del sistema (TS Mode)...");
|
|
21
|
+
|
|
22
|
+
const libPath = path.resolve(frameworkRoot, 'src/lib');
|
|
23
|
+
|
|
24
|
+
const parseJSDoc = (filename: string, content: string): DocStructure => {
|
|
25
|
+
const fileDoc: DocStructure = {
|
|
26
|
+
name: filename,
|
|
27
|
+
description: "Sin descripción.",
|
|
28
|
+
methods: []
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const cleanJSDoc = (raw: string) => {
|
|
32
|
+
return raw
|
|
33
|
+
.split('\n')
|
|
34
|
+
.map(line => {
|
|
35
|
+
return line.trim().replace(/^\*+\s?/, '');
|
|
36
|
+
})
|
|
37
|
+
.filter(line => line !== '')
|
|
38
|
+
.join('\n');
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const commentRegex = /\/\*\*([\s\S]*?)\*\//g;
|
|
42
|
+
let match;
|
|
43
|
+
|
|
44
|
+
while ((match = commentRegex.exec(content)) !== null) {
|
|
45
|
+
const rawComment = match[1];
|
|
46
|
+
const cleanComment = cleanJSDoc(rawComment);
|
|
47
|
+
|
|
48
|
+
const nextCodeIndex = commentRegex.lastIndex;
|
|
49
|
+
const codeSnippet = content.substring(nextCodeIndex, nextCodeIndex + 200);
|
|
50
|
+
|
|
51
|
+
const isClass = /@class/.test(cleanComment) || /^\s*export\s+class/.test(codeSnippet);
|
|
52
|
+
|
|
53
|
+
if (isClass) {
|
|
54
|
+
const descMatch = cleanComment.match(/@description\s+([\s\S]*?)(?=@|$)/i);
|
|
55
|
+
if (descMatch) {
|
|
56
|
+
fileDoc.description = descMatch[1].trim();
|
|
57
|
+
} else {
|
|
58
|
+
fileDoc.description = cleanComment.split('\n')[0];
|
|
59
|
+
}
|
|
60
|
+
const classNameMatch = codeSnippet.match(/class\s+(\w+)/);
|
|
61
|
+
if (classNameMatch) fileDoc.name = classNameMatch[1];
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
let methodName = null;
|
|
66
|
+
|
|
67
|
+
const methodTagMatch = cleanComment.match(/@method\s+(\w+)/i);
|
|
68
|
+
if (methodTagMatch) {
|
|
69
|
+
methodName = methodTagMatch[1];
|
|
70
|
+
} else if (!codeSnippet.match(/^\s*constructor/)) {
|
|
71
|
+
const codeMatch = codeSnippet.match(/(?:public|private|protected)\s+(?:async\s+)?(\w+)/);
|
|
72
|
+
if (codeMatch) {
|
|
73
|
+
methodName = codeMatch[1];
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (methodName) {
|
|
78
|
+
let description = "";
|
|
79
|
+
const descMatch = cleanComment.match(/@description\s+([\s\S]*?)(?=@|$)/i);
|
|
80
|
+
if (descMatch) {
|
|
81
|
+
description = descMatch[1].trim();
|
|
82
|
+
} else {
|
|
83
|
+
const textLines = cleanComment.split('\n').filter(l => !l.startsWith('@'));
|
|
84
|
+
description = textLines.join(' ').trim() || "Sin descripción";
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
let example = null;
|
|
88
|
+
const exampleMatch = cleanComment.match(/@example([\s\S]*?)(?=@|$)/i);
|
|
89
|
+
if (exampleMatch) {
|
|
90
|
+
example = exampleMatch[1]
|
|
91
|
+
.replace(/```ts|```/g, '')
|
|
92
|
+
.trim();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
fileDoc.methods.push({
|
|
96
|
+
name: methodName,
|
|
97
|
+
description: description,
|
|
98
|
+
example: example
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return fileDoc;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
if (!fs.existsSync(libPath)) {
|
|
107
|
+
logger.error(`No se encuentra la carpeta de librerías: ${libPath}`);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const files = fs.readdirSync(libPath).filter(f => f.endsWith('.ts'));
|
|
112
|
+
|
|
113
|
+
if (files.length === 0) {
|
|
114
|
+
logger.warn("No se encontraron archivos .ts en /src/lib para documentar.");
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const fileDocs = files.map(file => {
|
|
118
|
+
return parseJSDoc(file, fs.readFileSync(path.join(libPath, file), 'utf8'));
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
const systemDocs: DocStructure = {
|
|
122
|
+
name: 'TyrContext (Kernel)',
|
|
123
|
+
description: 'Utilidades globales inyectadas en cada comando. Accesibles destructurando el contexto.',
|
|
124
|
+
methods: [
|
|
125
|
+
{
|
|
126
|
+
name: 'run',
|
|
127
|
+
description: 'Ejecuta otro comando del sistema programáticamente (Composición de comandos). Útil para que un comando invoque a otros.',
|
|
128
|
+
example: `
|
|
129
|
+
// Llama al comando 'test' pasándole argumentos adicionales
|
|
130
|
+
const secret = "123";
|
|
131
|
+
args.push(secret);
|
|
132
|
+
await run('test', args);`.trim()
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
name: 'task',
|
|
136
|
+
description: 'Helper que envuelve una operación crítica. Si falla, el framework captura el error, añade contexto y lo muestra limpio en consola. Elimina la necesidad de try/catch manuales.',
|
|
137
|
+
example: `
|
|
138
|
+
// Ejemplo: Tarea asíncrona que retorna un valor
|
|
139
|
+
const buildId = await task('Compilando proyecto', async () => {
|
|
140
|
+
return await shell.exec('npm run build');
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// Si falla, el log dirá: "Falló la tarea: Compilando proyecto"`.trim()
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
name: 'fail',
|
|
147
|
+
description: 'Detiene la ejecución del comando inmediatamente lanzando un error controlado. Permite añadir una "sugerencia" para ayudar al usuario a solucionarlo.',
|
|
148
|
+
example: `
|
|
149
|
+
// Úsalo para validaciones lógicas
|
|
150
|
+
if (!fs.existsSync('./package.json')) {
|
|
151
|
+
fail(
|
|
152
|
+
'No se encuentra el archivo package de npm',
|
|
153
|
+
'Ejecuta "npm init -y" para generar uno.'
|
|
154
|
+
);
|
|
155
|
+
}`.trim()
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
name: 'logger',
|
|
159
|
+
description: 'Sistema de logs estandarizado con colores y formatos.',
|
|
160
|
+
example: `logger.info('Iniciando...');\nlogger.success('Creado');\nlogger.warn('Cuidado');`
|
|
161
|
+
}
|
|
162
|
+
]
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const docs = [systemDocs, ...fileDocs];
|
|
166
|
+
|
|
167
|
+
const html = `
|
|
168
|
+
<!DOCTYPE html>
|
|
169
|
+
<html>
|
|
170
|
+
<head>
|
|
171
|
+
<meta charset="utf-8">
|
|
172
|
+
<title>Tyr Docs</title>
|
|
173
|
+
<style>
|
|
174
|
+
body { font-family: 'Segoe UI', sans-serif; background: #222; color: #eee; padding: 20px; display: flex; margin: 0; }
|
|
175
|
+
nav { width: 220px; border-right: 1px solid #444; margin-right: 20px; padding-right: 20px; height: 100vh; overflow-y: auto; position: sticky; top: 0; }
|
|
176
|
+
a { color: #4db8ff; text-decoration: none; display: block; margin: 8px 0; padding: 5px; border-radius: 4px; transition: 0.2s; }
|
|
177
|
+
a:hover { background: #333; }
|
|
178
|
+
main { flex: 1; overflow-y: auto; }
|
|
179
|
+
.card { background: #2d2d2d; padding: 20px; margin-bottom: 30px; border-radius: 8px; border: 1px solid #333; }
|
|
180
|
+
h2 { border-bottom: 1px solid #444; padding-bottom: 10px; margin-top: 0; color: #fff; }
|
|
181
|
+
.method { margin-top: 25px; padding-left: 15px; border-left: 3px solid #4db8ff; }
|
|
182
|
+
h3 { margin: 0 0 5px 0; color: #4db8ff; font-family: monospace; font-size: 1.2em; }
|
|
183
|
+
.desc { color: #ccc; margin-bottom: 10px; }
|
|
184
|
+
pre { background: #1a1a1a; padding: 15px; border-radius: 5px; overflow-x: auto; border: 1px solid #444; color: #ce9178; font-family: monospace; white-space: pre-wrap; }
|
|
185
|
+
.tag-ts { background: #007acc; color: white; padding: 2px 6px; border-radius: 3px; font-size: 0.7em; margin-left: 10px; vertical-align: middle; }
|
|
186
|
+
.prompt-box { background: #1a1a1a; border: 2px solid #4db8ff; padding: 25px; border-radius: 8px; margin-top: 40px; position: relative; }
|
|
187
|
+
.prompt-box h2 { color: #4db8ff; margin-top: 0; border: none; }
|
|
188
|
+
.copy-btn { position: absolute; top: 20px; right: 20px; background: #4db8ff; color: #000; border: none; padding: 8px 16px; border-radius: 5px; cursor: pointer; font-weight: bold; transition: 0.2s; }
|
|
189
|
+
.copy-btn:hover { background: #6dc9ff; }
|
|
190
|
+
.copy-btn:active { background: #2da3e0; }
|
|
191
|
+
</style>
|
|
192
|
+
</head>
|
|
193
|
+
<body>
|
|
194
|
+
<nav>
|
|
195
|
+
<h3 style="color: #888; text-transform: uppercase; font-size: 0.8rem;">Módulos TS</h3>
|
|
196
|
+
${docs.map(d => `<a href="#${d.name}">📦 ${d.name.replace('.ts', '')}</a>`).join('')}
|
|
197
|
+
<a href="#ai-generator" style="margin-top: 20px; background: #4db8ff; color: #000; font-weight: bold;">🤖 Generador IA</a>
|
|
198
|
+
</nav>
|
|
199
|
+
<main>
|
|
200
|
+
${docs.map(d => `
|
|
201
|
+
<div id="${d.name}" class="card">
|
|
202
|
+
<h2>${d.name} <span class="tag-ts">TS</span></h2>
|
|
203
|
+
<p style="font-size: 1.1em; color: #bbb;">${d.description}</p>
|
|
204
|
+
${d.methods.map(m => `
|
|
205
|
+
<div class="method">
|
|
206
|
+
<h3>${m.name}()</h3>
|
|
207
|
+
<p class="desc">${m.description}</p>
|
|
208
|
+
${m.example ? `<pre>${m.example}</pre>` : ''}
|
|
209
|
+
</div>
|
|
210
|
+
`).join('')}
|
|
211
|
+
</div>
|
|
212
|
+
`).join('')}
|
|
213
|
+
|
|
214
|
+
<div id="ai-generator" class="prompt-box">
|
|
215
|
+
<h2>🤖 Generador de Comandos con IA</h2>
|
|
216
|
+
<p style="color: #bbb; margin-bottom: 20px;">
|
|
217
|
+
Describe qué debe hacer tu comando y la IA lo generará automáticamente usando la documentación del framework.
|
|
218
|
+
</p>
|
|
219
|
+
<div style="margin-bottom: 15px;">
|
|
220
|
+
<label style="display: block; color: #4db8ff; margin-bottom: 5px; font-weight: bold;">Nombre del comando</label>
|
|
221
|
+
<input type="text" id="cmd-name" placeholder="mi-comando"
|
|
222
|
+
style="width: 100%; padding: 10px; background: #2d2d2d; border: 1px solid #444; color: #eee; border-radius: 5px; font-size: 1em; box-sizing: border-box;" />
|
|
223
|
+
</div>
|
|
224
|
+
<div style="margin-bottom: 15px;">
|
|
225
|
+
<label style="display: block; color: #4db8ff; margin-bottom: 5px; font-weight: bold;">Describe qué debe hacer el comando</label>
|
|
226
|
+
<textarea id="cmd-prompt" rows="6" placeholder="Ej: Crea un comando que liste todos los contenedores Docker activos y muestre su estado en una tabla formateada..."
|
|
227
|
+
style="width: 100%; padding: 10px; background: #2d2d2d; border: 1px solid #444; color: #eee; border-radius: 5px; font-size: 1em; resize: vertical; box-sizing: border-box;"></textarea>
|
|
228
|
+
</div>
|
|
229
|
+
<button id="generate-btn" onclick="generateCommand()"
|
|
230
|
+
style="background: #4db8ff; color: #000; border: none; padding: 12px 24px; border-radius: 5px; cursor: pointer; font-weight: bold; font-size: 1em; transition: 0.2s; width: 100%;">
|
|
231
|
+
Generar Comando
|
|
232
|
+
</button>
|
|
233
|
+
<div id="gen-status" style="margin-top: 15px; display: none; padding: 15px; border-radius: 5px;"></div>
|
|
234
|
+
</div>
|
|
235
|
+
</main>
|
|
236
|
+
<script>
|
|
237
|
+
async function generateCommand() {
|
|
238
|
+
const name = document.getElementById('cmd-name').value.trim();
|
|
239
|
+
const prompt = document.getElementById('cmd-prompt').value.trim();
|
|
240
|
+
const btn = document.getElementById('generate-btn');
|
|
241
|
+
const status = document.getElementById('gen-status');
|
|
242
|
+
|
|
243
|
+
if (!name || !prompt) {
|
|
244
|
+
status.style.display = 'block';
|
|
245
|
+
status.style.background = '#4a2020';
|
|
246
|
+
status.style.border = '1px solid #ff4444';
|
|
247
|
+
status.textContent = 'Rellena ambos campos.';
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
btn.disabled = true;
|
|
252
|
+
btn.textContent = 'Generando...';
|
|
253
|
+
btn.style.background = '#888';
|
|
254
|
+
status.style.display = 'block';
|
|
255
|
+
status.style.background = '#1a1a2e';
|
|
256
|
+
status.style.border = '1px solid #4db8ff';
|
|
257
|
+
status.textContent = 'Enviando prompt a la IA... Esto puede tardar unos segundos.';
|
|
258
|
+
|
|
259
|
+
try {
|
|
260
|
+
const res = await fetch('/generate', {
|
|
261
|
+
method: 'POST',
|
|
262
|
+
headers: { 'Content-Type': 'application/json' },
|
|
263
|
+
body: JSON.stringify({ name, prompt })
|
|
264
|
+
});
|
|
265
|
+
const data = await res.json();
|
|
266
|
+
|
|
267
|
+
if (data.success) {
|
|
268
|
+
status.style.background = '#1a2e1a';
|
|
269
|
+
status.style.border = '1px solid #4ade80';
|
|
270
|
+
status.textContent = data.message;
|
|
271
|
+
} else {
|
|
272
|
+
status.style.background = '#4a2020';
|
|
273
|
+
status.style.border = '1px solid #ff4444';
|
|
274
|
+
status.textContent = data.message;
|
|
275
|
+
}
|
|
276
|
+
} catch (e) {
|
|
277
|
+
status.style.background = '#4a2020';
|
|
278
|
+
status.style.border = '1px solid #ff4444';
|
|
279
|
+
status.textContent = 'Error de conexión con el servidor.';
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
btn.disabled = false;
|
|
283
|
+
btn.textContent = 'Generar Comando';
|
|
284
|
+
btn.style.background = '#4db8ff';
|
|
285
|
+
}
|
|
286
|
+
</script>
|
|
287
|
+
</body>
|
|
288
|
+
</html>`;
|
|
289
|
+
|
|
290
|
+
const PORT = 3000;
|
|
291
|
+
const server = http.createServer(async (req, res) => {
|
|
292
|
+
if (req.method === 'POST' && req.url === '/generate') {
|
|
293
|
+
let body = '';
|
|
294
|
+
req.on('data', (chunk: Buffer) => { body += chunk.toString(); });
|
|
295
|
+
req.on('end', async () => {
|
|
296
|
+
try {
|
|
297
|
+
const { name, prompt } = JSON.parse(body);
|
|
298
|
+
if (!name || !prompt) {
|
|
299
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
300
|
+
res.end(JSON.stringify({ success: false, message: 'Faltan campos obligatorios.' }));
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
await run('ai', [name, prompt]);
|
|
305
|
+
|
|
306
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
307
|
+
res.end(JSON.stringify({ success: true, message: `Comando '${name}' generado correctamente en src/commands/${name}.tyr.ts` }));
|
|
308
|
+
} catch (e: any) {
|
|
309
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
310
|
+
res.end(JSON.stringify({ success: false, message: e.message || 'Error al generar el comando.' }));
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
317
|
+
res.end(html);
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
server.listen(PORT, () => {
|
|
321
|
+
logger.success(`Documentación TS lista en: http://localhost:${PORT}`);
|
|
322
|
+
logger.info("Presiona Ctrl+C para detener.");
|
|
323
|
+
});
|
|
324
|
+
};
|
|
325
325
|
};
|