g360-cli 1.4.0 → 1.5.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/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 +186 -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 +2 -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 +4 -4
- package/src/commands/bring.js +81 -6
- package/src/commands/list.js +79 -9
- 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
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
--g360-bg: #0b1220;
|
|
3
|
+
--g360-surface: #151e2e;
|
|
4
|
+
--g360-accent: #00d084;
|
|
5
|
+
--g360-text: #f0f4f8;
|
|
6
|
+
--g360-muted: #94a3b8;
|
|
7
|
+
--g360-glass: rgba(21, 30, 46, 0.75);
|
|
8
|
+
--g360-blur: 16px;
|
|
9
|
+
--g360-rounded: 16px;
|
|
10
|
+
--g360-font: system-ui, -apple-system, sans-serif;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
14
|
+
|
|
15
|
+
body {
|
|
16
|
+
font-family: var(--g360-font);
|
|
17
|
+
background: var(--g360-bg);
|
|
18
|
+
color: var(--g360-text);
|
|
19
|
+
line-height: 1.6;
|
|
20
|
+
-webkit-font-smoothing: antialiased;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.app {
|
|
24
|
+
min-height: 100vh;
|
|
25
|
+
display: flex;
|
|
26
|
+
flex-direction: column;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.container {
|
|
30
|
+
max-width: 480px;
|
|
31
|
+
margin: 0 auto;
|
|
32
|
+
padding: 24px 16px;
|
|
33
|
+
flex: 1;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.g360-header {
|
|
37
|
+
background: var(--g360-surface);
|
|
38
|
+
border-bottom: 1px solid rgba(255,255,255,0.08);
|
|
39
|
+
padding: 16px 24px;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.header-content {
|
|
43
|
+
max-width: 480px;
|
|
44
|
+
margin: 0 auto;
|
|
45
|
+
display: flex;
|
|
46
|
+
align-items: baseline;
|
|
47
|
+
gap: 12px;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.logo {
|
|
51
|
+
font-size: 24px;
|
|
52
|
+
font-weight: 800;
|
|
53
|
+
color: var(--g360-accent);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.subtitle {
|
|
57
|
+
font-size: 14px;
|
|
58
|
+
color: var(--g360-muted);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
h1 { font-size: 24px; font-weight: 700; margin-bottom: 8px; }
|
|
62
|
+
|
|
63
|
+
.muted { color: var(--g360-muted); font-size: 14px; }
|
|
64
|
+
|
|
65
|
+
.g360-footer {
|
|
66
|
+
text-align: center;
|
|
67
|
+
padding: 12px;
|
|
68
|
+
font-size: 11px;
|
|
69
|
+
color: var(--g360-muted);
|
|
70
|
+
background: var(--g360-surface);
|
|
71
|
+
border-top: 1px solid rgba(255,255,255,0.08);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.g360-loading-overlay {
|
|
75
|
+
position: fixed;
|
|
76
|
+
inset: 0;
|
|
77
|
+
background: rgba(11, 18, 32, 0.9);
|
|
78
|
+
display: flex;
|
|
79
|
+
flex-direction: column;
|
|
80
|
+
align-items: center;
|
|
81
|
+
justify-content: center;
|
|
82
|
+
gap: 16px;
|
|
83
|
+
z-index: 1000;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.g360-loading-spinner {
|
|
87
|
+
width: 40px;
|
|
88
|
+
height: 40px;
|
|
89
|
+
border: 3px solid var(--g360-surface);
|
|
90
|
+
border-top-color: var(--g360-accent);
|
|
91
|
+
border-radius: 50%;
|
|
92
|
+
animation: spin 0.8s linear infinite;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
@keyframes spin { to { transform: rotate(360deg); } }
|
|
96
|
+
|
|
97
|
+
.g360-card {
|
|
98
|
+
background: var(--g360-surface);
|
|
99
|
+
border-radius: var(--g360-rounded);
|
|
100
|
+
padding: 16px;
|
|
101
|
+
border: 1px solid rgba(255,255,255,0.08);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.g360-glass {
|
|
105
|
+
background: var(--g360-glass);
|
|
106
|
+
backdrop-filter: blur(var(--g360-blur));
|
|
107
|
+
-webkit-backdrop-filter: blur(var(--g360-blur));
|
|
108
|
+
border: 1px solid rgba(255,255,255,0.1);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.g360-btn {
|
|
112
|
+
background: var(--g360-accent);
|
|
113
|
+
color: var(--g360-bg);
|
|
114
|
+
border: none;
|
|
115
|
+
border-radius: 8px;
|
|
116
|
+
padding: 10px 20px;
|
|
117
|
+
font-size: 14px;
|
|
118
|
+
font-weight: 700;
|
|
119
|
+
cursor: pointer;
|
|
120
|
+
transition: opacity 0.2s;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.g360-btn:hover { opacity: 0.85; }
|
|
124
|
+
|
|
125
|
+
@media (prefers-color-scheme: light) {
|
|
126
|
+
:root {
|
|
127
|
+
--g360-bg: #ffffff;
|
|
128
|
+
--g360-surface: #f5f5f5;
|
|
129
|
+
--g360-muted: #6b7280;
|
|
130
|
+
--g360-text: #1f2937;
|
|
131
|
+
--g360-glass: rgba(255, 255, 255, 0.75);
|
|
132
|
+
}
|
|
133
|
+
}
|
package/src/cli.js
CHANGED
|
@@ -31,7 +31,7 @@ program
|
|
|
31
31
|
.command('init')
|
|
32
32
|
.argument('<name>', 'Project name')
|
|
33
33
|
.option('-t, --template <type>', 'Project template type', 'web-pwa')
|
|
34
|
-
.option('-s, --skill <skill>', 'Skill to use (corporativo, corporativo-movil, moderno, moderno-movil, minimalista, custom, flet-desktop)', 'corporativo-movil')
|
|
34
|
+
.option('-s, --skill <skill>', 'Skill to use (corporativo, corporativo-movil, moderno, moderno-movil, minimalista, custom, flet-desktop, flet-desktop-corporativo, cipsa, cipsa-movil)', 'corporativo-movil')
|
|
35
35
|
.option('-d, --dir <path>', 'Target directory', '.')
|
|
36
36
|
.option('--dry-run', 'Preview without creating files')
|
|
37
37
|
.option('--force', 'Overwrite existing files')
|
|
@@ -48,7 +48,7 @@ program
|
|
|
48
48
|
|
|
49
49
|
program
|
|
50
50
|
.command('bring')
|
|
51
|
-
.argument('[asset]', '
|
|
51
|
+
.argument('[asset]', 'Asset to bring: brand, brand/cipsa, brand/g360, components, templates, skills, all')
|
|
52
52
|
.option('-p, --path <path>', 'Target path', '.')
|
|
53
53
|
.option('--dry-run', 'Preview without copying')
|
|
54
54
|
.option('--force', 'Overwrite existing files')
|
|
@@ -56,7 +56,7 @@ program
|
|
|
56
56
|
|
|
57
57
|
program
|
|
58
58
|
.command('list')
|
|
59
|
-
.argument('[type]', 'Type to list (templates/components/skills/all)', 'all')
|
|
59
|
+
.argument('[type]', 'Type to list (templates/components/skills/snippets/brands/all)', 'all')
|
|
60
60
|
.option('--json', 'Output as JSON')
|
|
61
61
|
.action(list);
|
|
62
62
|
|
|
@@ -100,7 +100,7 @@ program
|
|
|
100
100
|
program
|
|
101
101
|
.command('convert')
|
|
102
102
|
.argument('[path]', 'Path to convert', '.')
|
|
103
|
-
.option('-s, --skill <skill>', 'Skill to apply', 'corporativo-movil')
|
|
103
|
+
.option('-s, --skill <skill>', 'Skill to apply (corporativo, corporativo-movil, moderno, moderno-movil, minimalista, custom, flet-desktop, flet-desktop-corporativo, cipsa, cipsa-movil)', 'corporativo-movil')
|
|
104
104
|
.option('--dry-run', 'Preview without applying')
|
|
105
105
|
.option('--restructure', 'Restructure project files')
|
|
106
106
|
.option('--force', 'Force dangerous changes')
|
package/src/commands/bring.js
CHANGED
|
@@ -10,7 +10,7 @@ export async function bring(asset, options) {
|
|
|
10
10
|
const { path: targetPath = '.', dryRun = false, force = false } = options;
|
|
11
11
|
const targetDir = path.join(process.cwd(), targetPath);
|
|
12
12
|
const assetsPath = path.join(__dirname, '../assets');
|
|
13
|
-
|
|
13
|
+
|
|
14
14
|
console.log(chalk.bold.cyan('\n📦 G360 Asset Manager\n'));
|
|
15
15
|
|
|
16
16
|
if (!asset || asset === 'all') {
|
|
@@ -19,14 +19,89 @@ export async function bring(asset, options) {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
const assetPath = path.join(assetsPath, asset);
|
|
22
|
-
|
|
22
|
+
|
|
23
23
|
if (!fs.existsSync(assetPath)) {
|
|
24
24
|
console.error(chalk.red(`❌ Asset "${asset}" not found.`));
|
|
25
|
-
console.log(chalk.gray('\nRun "g360 list
|
|
25
|
+
console.log(chalk.gray('\nRun "g360 list all" to see available assets.'));
|
|
26
26
|
return;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
await copyAssets(assetPath, targetDir, dryRun, force);
|
|
30
|
+
|
|
31
|
+
if (asset.startsWith('brand')) {
|
|
32
|
+
const brandName = asset.split('/')[1];
|
|
33
|
+
if (brandName) {
|
|
34
|
+
await applyBrand(targetDir, brandName, dryRun);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function applyBrand(targetDir, brandName, dryRun) {
|
|
40
|
+
const brandConfigPath = path.join(targetDir, 'g360', 'brand', 'brand.json');
|
|
41
|
+
if (!fs.existsSync(brandConfigPath)) {
|
|
42
|
+
console.log(chalk.yellow(`\n⚠ brand.json not found. Run 'g360 bring brand' first.`));
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
let brandConfig;
|
|
47
|
+
try {
|
|
48
|
+
brandConfig = fs.readJsonSync(brandConfigPath);
|
|
49
|
+
} catch {
|
|
50
|
+
console.log(chalk.yellow(`\n⚠ Could not parse brand.json.`));
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const brand = brandConfig.brands?.[brandName];
|
|
55
|
+
if (!brand) {
|
|
56
|
+
console.log(chalk.yellow(`\n⚠ Brand "${brandName}" not found in brand.json`));
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const skillCandidates = [
|
|
61
|
+
path.join(targetDir, 'skill.json'),
|
|
62
|
+
path.join(targetDir, 'src', 'core', 'skill.json'),
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
let skillPath = null;
|
|
66
|
+
for (const c of skillCandidates) {
|
|
67
|
+
if (fs.existsSync(c)) {
|
|
68
|
+
skillPath = c;
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (!skillPath) {
|
|
74
|
+
console.log(chalk.yellow(`\n⚠ No skill.json found. Create one with 'g360 init'.`));
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (dryRun) {
|
|
79
|
+
console.log(chalk.yellow(`\n📋 DRY RUN - Would update ${skillPath}`));
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const skill = fs.readJsonSync(skillPath);
|
|
84
|
+
skill.brand = brandName;
|
|
85
|
+
|
|
86
|
+
if (!skill.guidelines) skill.guidelines = {};
|
|
87
|
+
skill.guidelines.logo = brand.default_logo;
|
|
88
|
+
skill.guidelines.description = brand.description;
|
|
89
|
+
|
|
90
|
+
if (brand.signature) {
|
|
91
|
+
if (!skill.signature) skill.signature = {};
|
|
92
|
+
skill.signature.mode = brand.signature.mode;
|
|
93
|
+
skill.signature.text = brand.signature.text;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (brand.primary_color) {
|
|
97
|
+
if (!skill.colors) skill.colors = {};
|
|
98
|
+
skill.colors.accent = brand.primary_color;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
fs.writeJsonSync(skillPath, skill, { spaces: 2 });
|
|
102
|
+
console.log(chalk.green(`\n✅ Brand "${brandName}" applied to ${skillPath}`));
|
|
103
|
+
console.log(chalk.gray(` Logo: ${brand.default_logo}`));
|
|
104
|
+
console.log(chalk.gray(` Signature: ${brand.signature?.text || 'none'}`));
|
|
30
105
|
}
|
|
31
106
|
|
|
32
107
|
async function copyAssets(src, dest, dryRun, force) {
|
|
@@ -36,18 +111,18 @@ async function copyAssets(src, dest, dryRun, force) {
|
|
|
36
111
|
}
|
|
37
112
|
|
|
38
113
|
const progressBar = progress('Copying assets...');
|
|
39
|
-
|
|
114
|
+
|
|
40
115
|
try {
|
|
41
116
|
const items = fs.readdirSync(src);
|
|
42
117
|
for (const item of items) {
|
|
43
118
|
const srcPath = path.join(src, item);
|
|
44
119
|
const destPath = path.join(dest, 'g360', item);
|
|
45
|
-
|
|
120
|
+
|
|
46
121
|
if (fs.statSync(srcPath).isDirectory()) {
|
|
47
122
|
await fs.copy(srcPath, destPath, { overwrite: force });
|
|
48
123
|
}
|
|
49
124
|
}
|
|
50
|
-
|
|
125
|
+
|
|
51
126
|
progressBar.stop();
|
|
52
127
|
console.log(chalk.green('\n✅ Assets copied successfully!\n'));
|
|
53
128
|
} catch (error) {
|
package/src/commands/list.js
CHANGED
|
@@ -14,13 +14,15 @@ export async function list(type, options) {
|
|
|
14
14
|
const assets = {
|
|
15
15
|
templates: [],
|
|
16
16
|
components: [],
|
|
17
|
-
skills: []
|
|
17
|
+
skills: [],
|
|
18
|
+
brands: []
|
|
18
19
|
};
|
|
19
20
|
|
|
20
21
|
const templatesPath = path.join(assetsPath, 'templates');
|
|
21
22
|
const componentsPath = path.join(assetsPath, 'components');
|
|
22
|
-
const
|
|
23
|
-
const
|
|
23
|
+
const configPath = path.join(assetsPath, 'config');
|
|
24
|
+
const snippetsPath = path.join(assetsPath, 'snippets');
|
|
25
|
+
const brandPath = path.join(assetsPath, 'brand');
|
|
24
26
|
|
|
25
27
|
if (fs.existsSync(templatesPath)) {
|
|
26
28
|
assets.templates = fs.readdirSync(templatesPath);
|
|
@@ -28,11 +30,53 @@ export async function list(type, options) {
|
|
|
28
30
|
if (fs.existsSync(componentsPath)) {
|
|
29
31
|
assets.components = fs.readdirSync(componentsPath).filter(f => f.endsWith('.jsx') || f.endsWith('.js'));
|
|
30
32
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
+
|
|
34
|
+
if (fs.existsSync(configPath)) {
|
|
35
|
+
const g360SkillsPath = path.join(configPath, 'g360-skills.json');
|
|
36
|
+
if (fs.existsSync(g360SkillsPath)) {
|
|
37
|
+
try {
|
|
38
|
+
const skillsData = fs.readJsonSync(g360SkillsPath);
|
|
39
|
+
assets.skills = skillsData.skills.map(skill => ({
|
|
40
|
+
name: skill.name,
|
|
41
|
+
description: skill.description,
|
|
42
|
+
device: skill.device
|
|
43
|
+
}));
|
|
44
|
+
} catch (error) {
|
|
45
|
+
console.log(chalk.yellow(`Warning: Could not read skills config: ${error.message}`));
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (fs.existsSync(snippetsPath)) {
|
|
51
|
+
const snippetsJsonPath = path.join(snippetsPath, 'snippets.json');
|
|
52
|
+
if (fs.existsSync(snippetsJsonPath)) {
|
|
53
|
+
try {
|
|
54
|
+
const snippetsData = fs.readJsonSync(snippetsJsonPath);
|
|
55
|
+
assets.snippets = snippetsData.snippets.map(snippet => ({
|
|
56
|
+
name: snippet.name,
|
|
57
|
+
description: snippet.description,
|
|
58
|
+
language: snippet.language
|
|
59
|
+
}));
|
|
60
|
+
} catch (error) {
|
|
61
|
+
console.log(chalk.yellow(`Warning: Could not read snippets config: ${error.message}`));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
33
64
|
}
|
|
34
|
-
|
|
35
|
-
|
|
65
|
+
|
|
66
|
+
if (fs.existsSync(brandPath)) {
|
|
67
|
+
const brandConfigPath = path.join(brandPath, 'brand.json');
|
|
68
|
+
if (fs.existsSync(brandConfigPath)) {
|
|
69
|
+
try {
|
|
70
|
+
const brandData = fs.readJsonSync(brandConfigPath);
|
|
71
|
+
assets.brands = Object.entries(brandData.brands || {}).map(([key, val]) => ({
|
|
72
|
+
name: key,
|
|
73
|
+
description: val.description,
|
|
74
|
+
logo: val.default_logo
|
|
75
|
+
}));
|
|
76
|
+
} catch (error) {
|
|
77
|
+
console.log(chalk.yellow(`Warning: Could not read brand config: ${error.message}`));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
36
80
|
}
|
|
37
81
|
|
|
38
82
|
if (json) {
|
|
@@ -60,12 +104,38 @@ export async function list(type, options) {
|
|
|
60
104
|
|
|
61
105
|
if (!type || type === 'all' || type === 'skills') {
|
|
62
106
|
console.log(chalk.bold.yellow('\n⚡ Skills:'));
|
|
63
|
-
if (assets.
|
|
64
|
-
assets.
|
|
107
|
+
if (assets.skills.length) {
|
|
108
|
+
assets.skills.forEach(skill => {
|
|
109
|
+
console.log(chalk.gray(` - ${skill.name}`));
|
|
110
|
+
console.log(chalk.gray(` ${skill.description}`));
|
|
111
|
+
console.log(chalk.gray(` Device: ${skill.device}`));
|
|
112
|
+
});
|
|
65
113
|
} else {
|
|
66
114
|
console.log(chalk.gray(' No skills found'));
|
|
67
115
|
}
|
|
68
116
|
}
|
|
69
117
|
|
|
118
|
+
if (!type || type === 'all' || type === 'brands') {
|
|
119
|
+
console.log(chalk.bold.yellow('\n🎨 Brands:'));
|
|
120
|
+
if (assets.brands.length) {
|
|
121
|
+
assets.brands.forEach(b => console.log(chalk.gray(` - ${b.name}`)));
|
|
122
|
+
} else {
|
|
123
|
+
console.log(chalk.gray(' No brands found'));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (!type || type === 'all' || type === 'snippets') {
|
|
128
|
+
console.log(chalk.bold.yellow('\n📝 Snippets:'));
|
|
129
|
+
if (assets.snippets && assets.snippets.length) {
|
|
130
|
+
assets.snippets.forEach(snippet => {
|
|
131
|
+
console.log(chalk.gray(` - ${snippet.name}`));
|
|
132
|
+
console.log(chalk.gray(` ${snippet.description}`));
|
|
133
|
+
console.log(chalk.gray(` Language: ${snippet.language}`));
|
|
134
|
+
});
|
|
135
|
+
} else {
|
|
136
|
+
console.log(chalk.gray(' No snippets found'));
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
70
140
|
console.log();
|
|
71
141
|
}
|
|
@@ -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)) {
|