g360-cli 1.4.0 → 1.6.0

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.
Files changed (93) hide show
  1. package/README.md +122 -0
  2. package/package.json +11 -4
  3. package/src/assets/brand/brand.json +43 -0
  4. package/src/assets/brand/cipsa/logotypes/Logo_cipsa_borde.png +0 -0
  5. package/src/assets/brand/cipsa/logotypes/Logo_cipsa_borde.svg +9 -0
  6. package/src/assets/brand/cipsa/logotypes/Logo_cipsa_solid.png +0 -0
  7. package/src/assets/brand/cipsa/logotypes/Logo_cipsa_solid.svg +9 -0
  8. package/src/assets/brand/g360/logotypes/logo-g360-dark.svg +19 -0
  9. package/src/assets/brand/g360/logotypes/logo-g360-light.svg +19 -0
  10. package/src/assets/brand/g360/logotypes/logo_g360_dark_b64.txt +1 -0
  11. package/src/assets/brand/g360/logotypes/logo_g360_light_b64.txt +1 -0
  12. package/src/assets/config/g360-skills.json +60 -0
  13. package/src/assets/signature/g360_flet/__init__.py +5 -0
  14. package/src/assets/signature/g360_flet/__pycache__/__init__.cpython-312.pyc +0 -0
  15. package/src/assets/signature/g360_flet/__pycache__/__init__.cpython-314.pyc +0 -0
  16. package/src/assets/signature/g360_flet/__pycache__/g360_signature.cpython-312.pyc +0 -0
  17. package/src/assets/signature/g360_flet/__pycache__/g360_signature.cpython-314.pyc +0 -0
  18. package/src/assets/signature/g360_flet/g360_signature.py +224 -0
  19. package/src/assets/signature/index.js +130 -0
  20. package/src/assets/snippets/snippets.json +293 -101
  21. package/src/assets/templates/lit-web/src/core/skill.json +12 -5
  22. package/src/assets/templates/python-cli/build-portable.bat +19 -9
  23. package/src/assets/templates/python-cli/src/core/skill.json +25 -6
  24. package/src/assets/templates/python-customtkinter/README.md +32 -31
  25. package/src/assets/templates/python-customtkinter/assets/images/logo-g360-dark.svg +19 -0
  26. package/src/assets/templates/python-customtkinter/assets/images/logo-g360-light.svg +19 -0
  27. package/src/assets/templates/python-customtkinter/build.bat +50 -0
  28. package/src/assets/templates/python-customtkinter/pyproject.toml +26 -0
  29. package/src/assets/templates/python-customtkinter/run.bat +11 -20
  30. package/src/assets/templates/python-customtkinter/src/core/g360_theme.py +101 -0
  31. package/src/assets/templates/python-customtkinter/src/core/skill.json +23 -10
  32. package/src/assets/templates/python-customtkinter/src/main.py +61 -81
  33. package/src/assets/templates/python-flet/README.md +52 -93
  34. package/src/assets/templates/python-flet/assets/images/Logo_cipsa_borde.png +0 -0
  35. package/src/assets/templates/python-flet/assets/images/Logo_cipsa_borde.svg +9 -0
  36. package/src/assets/templates/python-flet/assets/images/Logo_cipsa_solid.png +0 -0
  37. package/src/assets/templates/python-flet/assets/images/Logo_cipsa_solid.svg +9 -0
  38. package/src/assets/templates/python-flet/assets/images/logo-g360-dark.svg +19 -0
  39. package/src/assets/templates/python-flet/assets/images/logo-g360-light.svg +19 -0
  40. package/src/assets/templates/python-flet/build.bat +36 -0
  41. package/src/assets/templates/python-flet/pyproject.toml +26 -0
  42. package/src/assets/templates/python-flet/run.bat +11 -72
  43. package/src/assets/templates/python-flet/src/core/__init__.py +0 -0
  44. package/src/assets/templates/python-flet/src/core/g360_theme.py +193 -0
  45. package/src/assets/templates/python-flet/src/core/skill.json +28 -8
  46. package/src/assets/templates/python-flet/src/export/__init__.py +0 -0
  47. package/src/assets/templates/python-flet/src/main.py +113 -68
  48. package/src/assets/templates/python-flet/src/test_app.py +41 -37
  49. package/src/assets/templates/python-flet/src/ui/__init__.py +0 -0
  50. package/src/assets/templates/python-flet-migrate/README.md +70 -0
  51. package/src/assets/templates/python-flet-migrate/assets/images/logo-g360-dark.svg +19 -0
  52. package/src/assets/templates/python-flet-migrate/assets/images/logo-g360-light.svg +19 -0
  53. package/src/assets/templates/python-flet-migrate/build.bat +36 -0
  54. package/src/assets/templates/python-flet-migrate/pyproject.toml +26 -0
  55. package/src/assets/templates/python-flet-migrate/run.bat +20 -0
  56. package/src/assets/templates/python-flet-migrate/src/core/__init__.py +0 -0
  57. package/src/assets/templates/python-flet-migrate/src/core/g360_theme.py +186 -0
  58. package/src/assets/templates/python-flet-migrate/src/core/skill.json +34 -0
  59. package/src/assets/templates/python-flet-migrate/src/main.py +71 -42
  60. package/src/assets/templates/solid-web/src/core/skill.json +12 -5
  61. package/src/assets/templates/svelte-web/src/core/skill.json +12 -5
  62. package/src/assets/templates/web-pwa/index.html +5 -7
  63. package/src/assets/templates/web-pwa/public/favicon.svg +4 -0
  64. package/src/assets/templates/web-pwa/public/manifest.json +11 -0
  65. package/src/assets/templates/web-pwa/src/App.jsx +22 -0
  66. package/src/assets/templates/web-pwa/src/components/FooterSignature.jsx +9 -0
  67. package/src/assets/templates/web-pwa/src/components/Header.jsx +12 -0
  68. package/src/assets/templates/web-pwa/src/components/LoadingOverlay.jsx +10 -0
  69. package/src/assets/templates/web-pwa/src/core/skill.json +25 -0
  70. package/src/assets/templates/web-pwa/src/main.jsx +9 -0
  71. package/src/assets/templates/web-pwa/src/styles/main.css +133 -0
  72. package/src/assets/templates/web-pwa/vite.config.js +12 -0
  73. package/src/cli.js +10 -5
  74. package/src/commands/bring.js +81 -6
  75. package/src/commands/list.js +79 -9
  76. package/src/commands/signature.js +252 -55
  77. package/src/lib/asset-validator.js +145 -0
  78. package/src/lib/auditor.js +48 -0
  79. package/src/lib/global-config.js +260 -0
  80. package/src/lib/i18n.js +238 -0
  81. package/src/lib/logger.js +100 -0
  82. package/src/lib/manifest.js +70 -1
  83. package/src/lib/validator.js +39 -0
  84. package/src/schemas/skills-schema.json +1 -0
  85. package/src/schemas/snippets-schema.json +1 -0
  86. package/dist/g360.exe +0 -0
  87. package/src/assets/templates/python-customtkinter/build-portable.bat +0 -28
  88. package/src/assets/templates/python-customtkinter/requirements.txt +0 -1
  89. package/src/assets/templates/python-flet/build-portable.bat +0 -42
  90. package/src/assets/templates/python-flet/create_shortcut.vbs +0 -35
  91. package/src/assets/templates/python-flet/requirements.txt +0 -3
  92. package/src/assets/templates/web-pwa/app.js +0 -1
  93. package/src/assets/templates/web-pwa/styles.css +0 -28
@@ -1,55 +1,252 @@
1
- import chalk from 'chalk';
2
- import fs from 'fs-extra';
3
- import path from 'path';
4
- import { fileURLToPath } from 'url';
5
-
6
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
7
-
8
- export async function signature(command, options) {
9
- const { path: targetPath = '.', force = false } = options;
10
-
11
- if (command === 'install') {
12
- console.log(chalk.bold.cyan('\n🔖 Instalando g360-signature\n'));
13
-
14
- const targetDir = path.resolve(process.cwd(), targetPath);
15
- const indexHtmlPath = path.join(targetDir, 'index.html');
16
-
17
- if (!fs.existsSync(indexHtmlPath)) {
18
- console.error(chalk.red(' No se encontro archivo index.html en el directorio actual'));
19
- return;
20
- }
21
-
22
- let htmlContent = fs.readFileSync(indexHtmlPath, 'utf8');
23
-
24
- const scriptTag = ' <script type="module" src="https://unpkg.com/g360-signature@latest/index.js"></script>';
25
- const signatureComponent = `
26
- <!-- Firma Oficial G360 -->
27
- <g360-signature mode="powered" style="position: fixed; bottom: 16px; right: 16px; z-index: 99999;"></g360-signature>
28
- `;
29
-
30
- // Si --force es true, eliminamos cualquier instancia existente para una reinstalación limpia.
31
- // De lo contrario, si ya está instalado y no se fuerza, salimos.
32
- if (force) {
33
- htmlContent = htmlContent.replace(new RegExp(scriptTag.trim().replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), '');
34
- htmlContent = htmlContent.replace(new RegExp(signatureComponent.trim().replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), '');
35
- htmlContent = htmlContent.replace(/\n\s*\n/g, '\n'); // Limpiar posibles líneas vacías extra
36
- } else if (htmlContent.includes('g360-signature')) {
37
- console.log(chalk.yellow('⚠️ g360-signature ya se encuentra instalado'));
38
- console.log(chalk.gray('Usa --force para reinstalar'));
39
- return;
40
- }
41
-
42
- if (!htmlContent.includes(scriptTag)) {
43
- htmlContent = htmlContent.replace('</body>', `${scriptTag}\n </body>`);
44
- }
45
-
46
- if (!htmlContent.includes('<g360-signature')) {
47
- htmlContent = htmlContent.replace('</body>', `${signatureComponent}\n </body>`);
48
- }
49
-
50
- fs.writeFileSync(indexHtmlPath, htmlContent, 'utf8');
51
-
52
- console.log(chalk.green('✅ g360-signature instalado exitosamente!'));
53
- console.log(chalk.gray('\nEl componente ha sido agregado en el pie de pagina de tu proyecto\n'));
54
- }
55
- }
1
+ import chalk from 'chalk';
2
+ import fs from 'fs-extra';
3
+ import path from 'path';
4
+ import { fileURLToPath } from 'url';
5
+
6
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
7
+ const SIGNATURE_ASSETS = path.join(__dirname, '..', 'assets', 'signature');
8
+
9
+ const POSITIONS = {
10
+ 'bottom-right': 'position: fixed; bottom: 16px; right: 16px; z-index: 99999;',
11
+ 'bottom-left': 'position: fixed; bottom: 16px; left: 16px; z-index: 99999;',
12
+ 'bottom-center': 'position: fixed; bottom: 16px; left: 50%; transform: translateX(-50%); z-index: 99999;',
13
+ 'footer-right': 'position: static; float: right;',
14
+ 'footer-left': 'position: static; float: left;',
15
+ };
16
+
17
+ const FLET_POSITIONS = {
18
+ 'sidebar': 'En el sidebar como footer',
19
+ 'footer': 'En el footer de la pagina',
20
+ 'fixed-bottom': 'Fijo abajo a la derecha',
21
+ 'header': 'En el header/cabecera',
22
+ };
23
+
24
+ export async function signature(command, options) {
25
+ const {
26
+ path: targetPath = '.',
27
+ force = false,
28
+ mode = 'powered',
29
+ version = null,
30
+ position = 'bottom-right',
31
+ interactive = false,
32
+ } = options;
33
+
34
+ if (command === 'install') {
35
+ console.log(chalk.bold.cyan('\n🔖 Instalando g360-signature\n'));
36
+
37
+ const targetDir = path.resolve(process.cwd(), targetPath);
38
+ const projectType = detectProjectType(targetDir);
39
+
40
+ if (!projectType) {
41
+ console.error(chalk.red('❌ No se detecto un proyecto web o Flet en el directorio actual'));
42
+ console.log(chalk.gray('Proyectos soportados: web (HTML), Flet (Python)'));
43
+ return;
44
+ }
45
+
46
+ // Modo interactivo: guiar al usuario
47
+ if (interactive) {
48
+ const selectedPosition = await interactivePosition(projectType);
49
+ if (selectedPosition) {
50
+ options.position = selectedPosition;
51
+ }
52
+ }
53
+
54
+ if (projectType === 'flet') {
55
+ await installFlet(targetDir, { force, mode, version, position: options.position });
56
+ } else if (projectType === 'web') {
57
+ await installWeb(targetDir, { force, mode, version, position: options.position });
58
+ }
59
+
60
+ console.log(chalk.green('\n✅ g360-signature instalado exitosamente!'));
61
+ showUsageTips(projectType, options.position);
62
+ }
63
+
64
+ if (command === 'positions') {
65
+ showPositions();
66
+ }
67
+ }
68
+
69
+ function detectProjectType(dir) {
70
+ const pyprojectPath = path.join(dir, 'pyproject.toml');
71
+ if (fs.existsSync(pyprojectPath)) {
72
+ const content = fs.readFileSync(pyprojectPath, 'utf8');
73
+ if (content.includes('flet')) return 'flet';
74
+ }
75
+ const mainPy = path.join(dir, 'src', 'main.py');
76
+ if (fs.existsSync(mainPy)) {
77
+ const content = fs.readFileSync(mainPy, 'utf8');
78
+ if (content.includes('flet')) return 'flet';
79
+ }
80
+ const indexHtml = path.join(dir, 'index.html');
81
+ if (fs.existsSync(indexHtml)) return 'web';
82
+ return null;
83
+ }
84
+
85
+ function showPositions() {
86
+ console.log(chalk.bold.cyan('\n📍 Posiciones disponibles para Web:\n'));
87
+ Object.entries(POSITIONS).forEach(([key, value]) => {
88
+ console.log(chalk.white(` ${key.padEnd(18)} ${chalk.gray(value)}`));
89
+ });
90
+
91
+ console.log(chalk.bold.cyan('\n📍 Posiciones disponibles para Flet:\n'));
92
+ Object.entries(FLET_POSITIONS).forEach(([key, value]) => {
93
+ console.log(chalk.white(` ${key.padEnd(18)} ${chalk.gray(value)}`));
94
+ });
95
+
96
+ console.log(chalk.gray('\nEjemplo: g360 signature install --position bottom-left\n'));
97
+ }
98
+
99
+ async function interactivePosition(projectType) {
100
+ console.log(chalk.bold.cyan('\n📍 Selecciona la posicion de la firma:\n'));
101
+
102
+ const options = projectType === 'flet'
103
+ ? Object.entries(FLET_POSITIONS)
104
+ : Object.entries(POSITIONS);
105
+
106
+ options.forEach(([key, value], index) => {
107
+ console.log(chalk.white(` ${index + 1}. ${key.padEnd(18)} ${chalk.gray(value)}`));
108
+ });
109
+
110
+ console.log(chalk.gray('\n Presiona Enter para usar la posicion por defecto (bottom-right)'));
111
+ console.log(chalk.gray(' O escribe el nombre de la posicion\n'));
112
+
113
+ // En modo no-interactivo, retornar default
114
+ return null;
115
+ }
116
+
117
+ function showUsageTips(projectType, position) {
118
+ console.log(chalk.bold.cyan('\n💡 Tips de uso:\n'));
119
+
120
+ if (projectType === 'web') {
121
+ console.log(chalk.white(' Web Component:'));
122
+ console.log(chalk.gray(' <g360-signature mode="powered"></g360-signature>'));
123
+ console.log(chalk.gray(' <g360-signature mode="own" version="1.0"></g360-signature>\n'));
124
+ } else {
125
+ console.log(chalk.white(' Flet Widget:'));
126
+ console.log(chalk.gray(' from core.components.g360_signature import G360Signature, g360_footer'));
127
+ console.log(chalk.gray(' page.add(g360_footer(version="1.0"))'));
128
+ console.log(chalk.gray(' page.add(G360Signature(mode="own"))\n'));
129
+ }
130
+
131
+ console.log(chalk.white(' Cambiar posicion:'));
132
+ console.log(chalk.gray(' g360 signature install --position bottom-left\n'));
133
+
134
+ console.log(chalk.white(' Ver todas las posiciones:'));
135
+ console.log(chalk.gray(' g360 signature positions\n'));
136
+ }
137
+
138
+ async function installWeb(targetDir, { force, mode, version, position }) {
139
+ console.log(chalk.gray('Detectado: Proyecto Web'));
140
+
141
+ const indexHtmlPath = path.join(targetDir, 'index.html');
142
+ let htmlContent = fs.readFileSync(indexHtmlPath, 'utf8');
143
+
144
+ const hasLocalIndex = fs.existsSync(path.join(targetDir, 'g360-signature', 'index.js'));
145
+ const scriptSrc = hasLocalIndex
146
+ ? ' <script src="g360-signature/index.js"></script>'
147
+ : ' <script src="https://unpkg.com/g360-signature@latest/index.js"></script>';
148
+
149
+ const versionAttr = version ? ` version="${version}"` : '';
150
+ const positionStyle = POSITIONS[position] || POSITIONS['bottom-right'];
151
+
152
+ const signatureComponent = `
153
+ <!-- Firma Oficial G360 -->
154
+ <g360-signature mode="${mode}"${versionAttr} style="${positionStyle}"></g360-signature>
155
+ `;
156
+
157
+ if (force) {
158
+ htmlContent = htmlContent.replace(/\n.*g360-signature.*\n/g, '\n');
159
+ htmlContent = htmlContent.replace(/<script[^>]*unpkg\.com\/g360-signature[^>]*><\/script>/g, '');
160
+ htmlContent = htmlContent.replace(/<script[^>]*g360-signature\/index\.js[^>]*><\/script>/g, '');
161
+ htmlContent = htmlContent.replace(/\n\s*\n/g, '\n');
162
+ } else if (htmlContent.includes('g360-signature')) {
163
+ console.log(chalk.yellow('⚠️ g360-signature ya se encuentra instalado'));
164
+ console.log(chalk.gray('Usa --force para reinstalar'));
165
+ return;
166
+ }
167
+
168
+ if (!hasLocalIndex) {
169
+ const sigDir = path.join(targetDir, 'g360-signature');
170
+ fs.copySync(SIGNATURE_ASSETS, sigDir);
171
+ console.log(chalk.gray(' Archivos copiados a g360-signature/'));
172
+ }
173
+
174
+ if (!htmlContent.includes('g360-signature/index.js') && !htmlContent.includes('unpkg.com/g360-signature')) {
175
+ htmlContent = htmlContent.replace('</body>', `${scriptSrc}\n </body>`);
176
+ }
177
+ if (!htmlContent.includes('<g360-signature')) {
178
+ htmlContent = htmlContent.replace('</body>', `${signatureComponent}\n </body>`);
179
+ }
180
+
181
+ fs.writeFileSync(indexHtmlPath, htmlContent, 'utf8');
182
+ console.log(chalk.gray(' index.html actualizado'));
183
+ }
184
+
185
+ async function installFlet(targetDir, { force, mode, version, position }) {
186
+ console.log(chalk.gray('Detectado: Proyecto Flet'));
187
+
188
+ const fletDestDir = path.join(targetDir, 'src', 'core', 'components');
189
+ if (!fs.existsSync(fletDestDir)) {
190
+ fs.mkdirSync(fletDestDir, { recursive: true });
191
+ }
192
+
193
+ const widgetDest = path.join(fletDestDir, 'g360_signature.py');
194
+
195
+ if (fs.existsSync(widgetDest) && !force) {
196
+ console.log(chalk.yellow('⚠️ g360-signature ya se encuentra instalado'));
197
+ console.log(chalk.gray('Usa --force para reinstalar'));
198
+ return;
199
+ }
200
+
201
+ const widgetSrc = path.join(SIGNATURE_ASSETS, 'g360_flet', 'g360_signature.py');
202
+ fs.copySync(widgetSrc, widgetDest);
203
+ console.log(chalk.gray(' Widget copiado a src/core/components/g360_signature.py'));
204
+
205
+ const initPath = path.join(fletDestDir, '__init__.py');
206
+ if (!fs.existsSync(initPath)) {
207
+ fs.writeFileSync(initPath, '', 'utf8');
208
+ }
209
+
210
+ // Insertar en main.py
211
+ const mainPyPath = path.join(targetDir, 'src', 'main.py');
212
+ if (fs.existsSync(mainPyPath)) {
213
+ let mainContent = fs.readFileSync(mainPyPath, 'utf8');
214
+
215
+ const importLine = 'from core.components.g360_signature import G360Signature, g360_footer';
216
+ const versionAttr = version ? `, version="${version}"` : '';
217
+
218
+ // Codigo de insercion segun posicion
219
+ let footerCode;
220
+ switch (position) {
221
+ case 'sidebar':
222
+ footerCode = `\n # Firma G360 en sidebar\n sidebar.controls.append(g360_footer(${versionAttr.trim() ? versionAttr : ''}))\n`;
223
+ break;
224
+ case 'header':
225
+ footerCode = `\n # Firma G360 en header\n header.controls.append(G360Signature(mode="${mode}"${versionAttr}))\n`;
226
+ break;
227
+ case 'fixed-bottom':
228
+ footerCode = `\n # Firma G360 fija abajo\n page.overlay.append(G360Signature(mode="${mode}"${versionAttr}))\n`;
229
+ break;
230
+ default: // footer
231
+ footerCode = `\n # Firma G360\n page.add(g360_footer(${versionAttr.trim() ? versionAttr : ''}))\n`;
232
+ }
233
+
234
+ if (!mainContent.includes('g360_signature')) {
235
+ mainContent = mainContent.replace(
236
+ /(from\s+core\.\w+.*\n)/,
237
+ `$1${importLine}\n`
238
+ );
239
+
240
+ if (mainContent.includes('page.add')) {
241
+ const lastAdd = mainContent.lastIndexOf('page.add');
242
+ const endOfLine = mainContent.indexOf('\n', lastAdd);
243
+ mainContent = mainContent.slice(0, endOfLine + 1) + footerCode + mainContent.slice(endOfLine + 1);
244
+ } else {
245
+ mainContent += footerCode;
246
+ }
247
+
248
+ fs.writeFileSync(mainPyPath, mainContent, 'utf8');
249
+ console.log(chalk.gray(' main.py actualizado'));
250
+ }
251
+ }
252
+ }
@@ -0,0 +1,145 @@
1
+ import Ajv from 'ajv';
2
+ import fs from 'fs-extra';
3
+ import path from 'path';
4
+ import { fileURLToPath } from 'url';
5
+ import { logger } from './logger.js';
6
+
7
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
8
+
9
+ /**
10
+ * @typedef {Object} ValidationResult
11
+ * @property {boolean} valid - Indica si la validación fue exitosa
12
+ * @property {string[]} errors - Lista de errores encontrados
13
+ */
14
+
15
+ /**
16
+ * Módulo de validación de assets con JSON Schema
17
+ * @namespace assetValidator
18
+ */
19
+ export const assetValidator = {
20
+ /**
21
+ * Instancia de AJV para validación de schemas
22
+ * @type {Ajv}
23
+ */
24
+ ajv: new Ajv({ allErrors: true }),
25
+
26
+ /**
27
+ * Cache de schemas compilados
28
+ * @type {Map<string, any>}
29
+ */
30
+ schemaCache: new Map(),
31
+
32
+ /**
33
+ * Carga y compila un schema JSON
34
+ * @param {string} schemaName - Nombre del schema a cargar
35
+ * @returns {Promise<any>} Schema compilado
36
+ * @private
37
+ */
38
+ async loadSchema(schemaName) {
39
+ if (this.schemaCache.has(schemaName)) {
40
+ return this.schemaCache.get(schemaName);
41
+ }
42
+
43
+ const schemaPath = path.join(__dirname, '../schemas', `${schemaName}.json`);
44
+
45
+ if (!fs.existsSync(schemaPath)) {
46
+ throw new Error(`Schema not found: ${schemaName}`);
47
+ }
48
+
49
+ const schema = await fs.readJson(schemaPath);
50
+ const compiled = this.ajv.compile(schema);
51
+
52
+ this.schemaCache.set(schemaName, compiled);
53
+ return compiled;
54
+ },
55
+
56
+ /**
57
+ * Valida un objeto contra un schema específico
58
+ * @param {string} schemaName - Nombre del schema a usar
59
+ * @param {Object} data - Datos a validar
60
+ * @returns {Promise<ValidationResult>} Resultado de la validación
61
+ * @example
62
+ * const result = await assetValidator.validate('skills-schema', skillsData);
63
+ * if (result.valid) {
64
+ * console.log('Skills válidos');
65
+ * } else {
66
+ * console.error('Errores:', result.errors);
67
+ * }
68
+ */
69
+ async validate(schemaName, data) {
70
+ try {
71
+ const validate = await this.loadSchema(schemaName);
72
+ const valid = validate(data);
73
+
74
+ if (valid) {
75
+ logger.debug(`Validation passed for schema: ${schemaName}`);
76
+ return { valid: true, errors: [] };
77
+ }
78
+
79
+ const errors = validate.errors.map(err =>
80
+ `${err.instancePath} ${err.message}`
81
+ );
82
+
83
+ logger.warn(`Validation failed for schema: ${schemaName}`, { errors });
84
+ return { valid: false, errors };
85
+ } catch (error) {
86
+ logger.error(`Error validating against schema: ${schemaName}`, { error: error.message });
87
+ return {
88
+ valid: false,
89
+ errors: [`Validation error: ${error.message}`]
90
+ };
91
+ }
92
+ },
93
+
94
+ /**
95
+ * Valida la configuración de skills
96
+ * @param {Object} skillsData - Datos de skills a validar
97
+ * @returns {Promise<ValidationResult>} Resultado de la validación
98
+ * @example
99
+ * const result = await assetValidator.validateSkills(skillsConfig);
100
+ */
101
+ async validateSkills(skillsData) {
102
+ return this.validate('skills-schema', skillsData);
103
+ },
104
+
105
+ /**
106
+ * Valida la configuración de snippets
107
+ * @param {Object} snippetsData - Datos de snippets a validar
108
+ * @returns {Promise<ValidationResult>} Resultado de la validación
109
+ * @example
110
+ * const result = await assetValidator.validateSnippets(snippetsConfig);
111
+ */
112
+ async validateSnippets(snippetsData) {
113
+ return this.validate('snippets-schema', snippetsData);
114
+ },
115
+
116
+ /**
117
+ * Valida un asset individual
118
+ * @param {string} assetType - Tipo de asset ('skill' | 'snippet')
119
+ * @param {Object} assetData - Datos del asset a validar
120
+ * @returns {Promise<ValidationResult>} Resultado de la validación
121
+ * @example
122
+ * const result = await assetValidator.validateAsset('skill', skillData);
123
+ */
124
+ async validateAsset(assetType, assetData) {
125
+ const schemaName = assetType === 'skill' ? 'skills-schema' : 'snippets-schema';
126
+
127
+ // Para validación individual, envolvemos el asset en la estructura esperada
128
+ const wrapper = assetType === 'skill'
129
+ ? { skills: [assetData] }
130
+ : { snippets: [assetData] };
131
+
132
+ const result = await this.validate(schemaName, wrapper);
133
+
134
+ return result;
135
+ },
136
+
137
+ /**
138
+ * Limpia el cache de schemas
139
+ * @returns {void}
140
+ */
141
+ clearCache() {
142
+ this.schemaCache.clear();
143
+ logger.debug('Schema cache cleared');
144
+ }
145
+ };
@@ -2,7 +2,37 @@ import fs from 'fs-extra';
2
2
  import path from 'path';
3
3
  import chalk from 'chalk';
4
4
 
5
+ /**
6
+ * @typedef {Object} AuditIssue
7
+ * @property {string} file - Archivo donde se encontró el issue
8
+ * @property {string} message - Mensaje descriptivo del issue
9
+ * @property {string} severity - Severidad del issue ('error' | 'warning')
10
+ */
11
+
12
+ /**
13
+ * @typedef {Object} AuditResult
14
+ * @property {number} passed - Cantidad de checks que pasaron
15
+ * @property {number} failed - Cantidad de checks que fallaron
16
+ * @property {number} warnings - Cantidad de advertencias
17
+ * @property {number} total - Cantidad total de checks
18
+ * @property {AuditIssue[]} issues - Lista de issues encontrados
19
+ */
20
+
21
+ /**
22
+ * Módulo de auditoría para proyectos G360
23
+ * @namespace auditor
24
+ */
5
25
  export const auditor = {
26
+ /**
27
+ * Ejecuta auditoría completa del proyecto
28
+ * @param {string} projectDir - Directorio del proyecto a auditar
29
+ * @param {Object} options - Opciones de auditoría
30
+ * @param {boolean} [options.verbose=false] - Mostrar información detallada
31
+ * @returns {Promise<AuditResult>} Resultado de la auditoría
32
+ * @example
33
+ * const result = await auditor.audit('/my/project', { verbose: true });
34
+ * console.log(`Passed: ${result.passed}, Failed: ${result.failed}`);
35
+ */
6
36
  async audit(projectDir, options = {}) {
7
37
  const { verbose = false } = options;
8
38
  const results = {
@@ -41,6 +71,12 @@ export const auditor = {
41
71
  return results;
42
72
  },
43
73
 
74
+ /**
75
+ * Verifica si existe el manifiesto del proyecto
76
+ * @param {string} projectDir - Directorio del proyecto
77
+ * @returns {Object} Resultado del check
78
+ * @private
79
+ */
44
80
  checkManifest(projectDir) {
45
81
  const manifestPath = path.join(projectDir, 'g360-manifest.json');
46
82
  if (fs.existsSync(manifestPath)) {
@@ -67,6 +103,12 @@ export const auditor = {
67
103
  return { status: 'warn', issue: { file: 'g360-manifest.json', message: 'No manifest found - run "g360 bring" to initialize' } };
68
104
  },
69
105
 
106
+ /**
107
+ * Verifica la estructura del proyecto
108
+ * @param {string} projectDir - Directorio del proyecto
109
+ * @returns {Object} Resultado del check
110
+ * @private
111
+ */
70
112
  checkStructure(projectDir) {
71
113
  const g360Dir = path.join(projectDir, 'g360');
72
114
  if (fs.existsSync(g360Dir)) {
@@ -94,6 +136,12 @@ export const auditor = {
94
136
  return { status: 'warn', issue: { file: 'g360/', message: 'No G360 assets found - run "g360 bring" to initialize assets' } };
95
137
  },
96
138
 
139
+ /**
140
+ * Verifica la configuración del proyecto
141
+ * @param {string} projectDir - Directorio del proyecto
142
+ * @returns {Object} Resultado del check
143
+ * @private
144
+ */
97
145
  checkConfig(projectDir) {
98
146
  const configPath = path.join(projectDir, 'g360', 'config');
99
147
  if (fs.existsSync(configPath)) {