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.
- package/README.md +122 -0
- package/package.json +11 -4
- package/src/assets/brand/brand.json +43 -0
- package/src/assets/brand/cipsa/logotypes/Logo_cipsa_borde.png +0 -0
- package/src/assets/brand/cipsa/logotypes/Logo_cipsa_borde.svg +9 -0
- package/src/assets/brand/cipsa/logotypes/Logo_cipsa_solid.png +0 -0
- package/src/assets/brand/cipsa/logotypes/Logo_cipsa_solid.svg +9 -0
- package/src/assets/brand/g360/logotypes/logo-g360-dark.svg +19 -0
- package/src/assets/brand/g360/logotypes/logo-g360-light.svg +19 -0
- package/src/assets/brand/g360/logotypes/logo_g360_dark_b64.txt +1 -0
- package/src/assets/brand/g360/logotypes/logo_g360_light_b64.txt +1 -0
- package/src/assets/config/g360-skills.json +60 -0
- package/src/assets/signature/g360_flet/__init__.py +5 -0
- package/src/assets/signature/g360_flet/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/assets/signature/g360_flet/__pycache__/__init__.cpython-314.pyc +0 -0
- package/src/assets/signature/g360_flet/__pycache__/g360_signature.cpython-312.pyc +0 -0
- package/src/assets/signature/g360_flet/__pycache__/g360_signature.cpython-314.pyc +0 -0
- package/src/assets/signature/g360_flet/g360_signature.py +224 -0
- package/src/assets/signature/index.js +130 -0
- package/src/assets/snippets/snippets.json +293 -101
- package/src/assets/templates/lit-web/src/core/skill.json +12 -5
- package/src/assets/templates/python-cli/build-portable.bat +19 -9
- package/src/assets/templates/python-cli/src/core/skill.json +25 -6
- package/src/assets/templates/python-customtkinter/README.md +32 -31
- package/src/assets/templates/python-customtkinter/assets/images/logo-g360-dark.svg +19 -0
- package/src/assets/templates/python-customtkinter/assets/images/logo-g360-light.svg +19 -0
- package/src/assets/templates/python-customtkinter/build.bat +50 -0
- package/src/assets/templates/python-customtkinter/pyproject.toml +26 -0
- package/src/assets/templates/python-customtkinter/run.bat +11 -20
- package/src/assets/templates/python-customtkinter/src/core/g360_theme.py +101 -0
- package/src/assets/templates/python-customtkinter/src/core/skill.json +23 -10
- package/src/assets/templates/python-customtkinter/src/main.py +61 -81
- package/src/assets/templates/python-flet/README.md +52 -93
- package/src/assets/templates/python-flet/assets/images/Logo_cipsa_borde.png +0 -0
- package/src/assets/templates/python-flet/assets/images/Logo_cipsa_borde.svg +9 -0
- package/src/assets/templates/python-flet/assets/images/Logo_cipsa_solid.png +0 -0
- package/src/assets/templates/python-flet/assets/images/Logo_cipsa_solid.svg +9 -0
- package/src/assets/templates/python-flet/assets/images/logo-g360-dark.svg +19 -0
- package/src/assets/templates/python-flet/assets/images/logo-g360-light.svg +19 -0
- package/src/assets/templates/python-flet/build.bat +36 -0
- package/src/assets/templates/python-flet/pyproject.toml +26 -0
- package/src/assets/templates/python-flet/run.bat +11 -72
- package/src/assets/templates/python-flet/src/core/__init__.py +0 -0
- package/src/assets/templates/python-flet/src/core/g360_theme.py +193 -0
- package/src/assets/templates/python-flet/src/core/skill.json +28 -8
- package/src/assets/templates/python-flet/src/export/__init__.py +0 -0
- package/src/assets/templates/python-flet/src/main.py +113 -68
- package/src/assets/templates/python-flet/src/test_app.py +41 -37
- package/src/assets/templates/python-flet/src/ui/__init__.py +0 -0
- package/src/assets/templates/python-flet-migrate/README.md +70 -0
- package/src/assets/templates/python-flet-migrate/assets/images/logo-g360-dark.svg +19 -0
- package/src/assets/templates/python-flet-migrate/assets/images/logo-g360-light.svg +19 -0
- package/src/assets/templates/python-flet-migrate/build.bat +36 -0
- package/src/assets/templates/python-flet-migrate/pyproject.toml +26 -0
- package/src/assets/templates/python-flet-migrate/run.bat +20 -0
- package/src/assets/templates/python-flet-migrate/src/core/__init__.py +0 -0
- package/src/assets/templates/python-flet-migrate/src/core/g360_theme.py +186 -0
- package/src/assets/templates/python-flet-migrate/src/core/skill.json +34 -0
- package/src/assets/templates/python-flet-migrate/src/main.py +71 -42
- package/src/assets/templates/solid-web/src/core/skill.json +12 -5
- package/src/assets/templates/svelte-web/src/core/skill.json +12 -5
- package/src/assets/templates/web-pwa/index.html +5 -7
- package/src/assets/templates/web-pwa/public/favicon.svg +4 -0
- package/src/assets/templates/web-pwa/public/manifest.json +11 -0
- package/src/assets/templates/web-pwa/src/App.jsx +22 -0
- package/src/assets/templates/web-pwa/src/components/FooterSignature.jsx +9 -0
- package/src/assets/templates/web-pwa/src/components/Header.jsx +12 -0
- package/src/assets/templates/web-pwa/src/components/LoadingOverlay.jsx +10 -0
- package/src/assets/templates/web-pwa/src/core/skill.json +25 -0
- package/src/assets/templates/web-pwa/src/main.jsx +9 -0
- package/src/assets/templates/web-pwa/src/styles/main.css +133 -0
- package/src/assets/templates/web-pwa/vite.config.js +12 -0
- package/src/cli.js +10 -5
- package/src/commands/bring.js +81 -6
- package/src/commands/list.js +79 -9
- package/src/commands/signature.js +252 -55
- package/src/lib/asset-validator.js +145 -0
- package/src/lib/auditor.js +48 -0
- package/src/lib/global-config.js +260 -0
- package/src/lib/i18n.js +238 -0
- package/src/lib/logger.js +100 -0
- package/src/lib/manifest.js +70 -1
- package/src/lib/validator.js +39 -0
- package/src/schemas/skills-schema.json +1 -0
- package/src/schemas/snippets-schema.json +1 -0
- package/dist/g360.exe +0 -0
- package/src/assets/templates/python-customtkinter/build-portable.bat +0 -28
- package/src/assets/templates/python-customtkinter/requirements.txt +0 -1
- package/src/assets/templates/python-flet/build-portable.bat +0 -42
- package/src/assets/templates/python-flet/create_shortcut.vbs +0 -35
- package/src/assets/templates/python-flet/requirements.txt +0 -3
- package/src/assets/templates/web-pwa/app.js +0 -1
- 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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
+
};
|
package/src/lib/auditor.js
CHANGED
|
@@ -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)) {
|