g360-cli 1.9.0 → 1.10.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.
@@ -4,88 +4,86 @@
4
4
  * Valida archivos ERP sin procesar completamente.
5
5
  */
6
6
 
7
- import { Command } from 'commander';
8
7
  import chalk from 'chalk';
9
8
  import path from 'path';
10
9
  import { fileURLToPath } from 'url';
10
+ import { spawn } from 'child_process';
11
11
  import fs from 'fs-extra';
12
12
 
13
13
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
14
14
 
15
- export = (program: Command) => {
16
- program
17
- .command('validate')
18
- .argument('<paths...>', 'Archivos o directorios a validar')
19
- .option('-r, --recursive', 'Buscar recursivamente en directorios')
20
- .action(async (paths, options) => {
21
- console.log(chalk.blue('\n🔍 Validando archivos ERP\n'));
22
-
23
- const filesToCheck: string[] = [];
24
- for (const rawPath of paths) {
25
- const p = path.resolve(rawPath);
26
- try {
27
- const stat = await fs.stat(p);
28
- if (stat.isFile()) {
29
- if (p.match(/\.(xls|xlsx|csv)$/i)) {
30
- filesToCheck.push(p);
31
- }
32
- } else if (stat.isDirectory()) {
33
- const pattern = options.recursive ? '**/*' : '*';
34
- const extPattern = /\.(xls|xlsx|csv)$/i;
35
- const entries = await fs.readdir(p, { withFileTypes: true });
36
- for (const entry of entries) {
37
- const fullPath = path.join(p, entry.name);
38
- if (entry.isFile() && extPattern.test(entry.name)) {
39
- filesToCheck.push(fullPath);
40
- } else if (entry.isDirectory() && options.recursive) {
41
- // Simplificado: escaneo recursivo básico
42
- const sub = await fs.readdir(fullPath, { withFileTypes: true });
43
- for (const subEntry of sub) {
44
- if (subEntry.isFile() && extPattern.test(subEntry.name)) {
45
- filesToCheck.push(path.join(fullPath, subEntry.name));
46
- }
47
- }
15
+ export async function validate(paths, options) {
16
+ const { recursive = false } = options;
17
+
18
+ console.log(chalk.blue('\n🔍 Validando archivos ERP\n'));
19
+
20
+ const filesToCheck = [];
21
+ for (const rawPath of paths) {
22
+ const p = path.resolve(rawPath);
23
+ try {
24
+ const stat = await fs.stat(p);
25
+ if (stat.isFile()) {
26
+ if (/\.(xls|xlsx|csv)$/i.test(p)) {
27
+ filesToCheck.push(p);
28
+ }
29
+ } else if (stat.isDirectory()) {
30
+ const pattern = recursive ? '**/*' : '*';
31
+ const extPattern = /\.(xls|xlsx|csv)$/i;
32
+ const entries = await fs.readdir(p, { withFileTypes: true });
33
+ for (const entry of entries) {
34
+ const fullPath = path.join(p, entry.name);
35
+ if (entry.isFile() && extPattern.test(entry.name)) {
36
+ filesToCheck.push(fullPath);
37
+ } else if (entry.isDirectory() && recursive) {
38
+ // Simplificado: escaneo recursivo básico
39
+ const sub = await fs.readdir(fullPath, { withFileTypes: true });
40
+ for (const subEntry of sub) {
41
+ if (subEntry.isFile() && extPattern.test(subEntry.name)) {
42
+ filesToCheck.push(path.join(fullPath, subEntry.name));
48
43
  }
49
44
  }
50
45
  }
51
- } catch (err) {
52
- console.warn(chalk.yellow(` ⚠ No se pudo acceder a ${p}: ${err.message}`));
53
46
  }
54
47
  }
48
+ } catch (err) {
49
+ console.warn(chalk.yellow(` ⚠ No se pudo acceder a ${p}: ${err.message}`));
50
+ }
51
+ }
55
52
 
56
- if (filesToCheck.length === 0) {
57
- console.warn(chalk.yellow(' No se encontraron archivos para validar'));
58
- return;
59
- }
53
+ if (filesToCheck.length === 0) {
54
+ console.warn(chalk.yellow(' No se encontraron archivos para validar'));
55
+ return;
56
+ }
60
57
 
61
- const results: Array<{ path: string; valid: boolean; missing: string[] }> = [];
58
+ const results = [];
62
59
 
63
- for (const file of filesToCheck) {
64
- try {
65
- const validation = await validateSingleFile(file);
66
- results.push(validation);
67
- } catch (err: any) {
68
- results.push({ path: file, valid: false, missing: [`ERROR: ${err.message}`] });
69
- }
70
- }
60
+ for (const file of filesToCheck) {
61
+ try {
62
+ const validation = await validateSingleFile(file);
63
+ results.push(validation);
64
+ } catch (err) {
65
+ results.push({ path: file, valid: false, missing: [`ERROR: ${err.message}`] });
66
+ }
67
+ }
71
68
 
72
- let validCount = 0;
73
- for (const res of results) {
74
- const status = res.valid ? chalk.green('✅') : chalk.red('❌');
75
- const filename = path.basename(res.path);
76
- console.log(`${status} ${filename} (${res.valid ? 'OK' : 'FALLÓ'})`);
77
- if (!res.valid && res.missing.length > 0) {
78
- console.log(chalk.gray(` Faltan: ${res.missing.join(', ')}`));
79
- }
80
- if (res.valid) validCount++;
81
- }
69
+ let validCount = 0;
70
+ for (const res of results) {
71
+ const status = res.valid ? chalk.green('✅') : chalk.red('❌');
72
+ const filename = path.basename(res.path);
73
+ console.log(`${status} ${filename} (${res.valid ? 'OK' : 'FALLÓ'})`);
74
+ if (!res.valid && res.missing.length > 0) {
75
+ console.log(chalk.gray(` Faltan: ${res.missing.join(', ')}`));
76
+ }
77
+ if (res.valid) validCount++;
78
+ }
82
79
 
83
- console.log(chalk.gray(`\n📊 Resumen: ${validCount}/${results.length} archivos válidos`));
84
- });
85
- };
80
+ console.log(chalk.gray(`\n📊 Resumen: ${validCount}/${results.length} archivos válidos`));
81
+ }
86
82
 
87
- async function validateSingleFile(filepath: string): Promise<{ path: string; valid: boolean; missing: string[] }> {
83
+ async function validateSingleFile(filepath) {
88
84
  const pyCode = `
85
+ import sys
86
+ sys.path.insert(0, '${path.join(__dirname, '..', 'py', 'src')}')
89
87
  from g360_core.scanner import ERPScanner
90
88
  from pathlib import Path
91
89
 
@@ -102,12 +100,11 @@ except Exception as e:
102
100
  `;
103
101
 
104
102
  try {
105
- const { runPython } = await import('../lib/python_runner.js');
106
103
  const result = await runPython(pyCode);
107
104
  const lines = result.stdout.split('\n').filter(l => l.trim());
108
105
 
109
106
  let valid = false;
110
- let missing: string[] = [];
107
+ let missing = [];
111
108
 
112
109
  for (const line of lines) {
113
110
  if (line.startsWith('VALID=')) {
@@ -120,7 +117,34 @@ except Exception as e:
120
117
  }
121
118
 
122
119
  return { path: filepath, valid, missing };
123
- } catch (err: any) {
120
+ } catch (err) {
124
121
  return { path: filepath, valid: false, missing: [err.message] };
125
122
  }
126
123
  }
124
+
125
+ function runPython(code) {
126
+ return new Promise((resolve, reject) => {
127
+ const pyExec = process.env.PYTHON || 'python3';
128
+ const proc = spawn(pyExec, ['-c', code], {
129
+ stdio: ['ignore', 'pipe', 'pipe'],
130
+ });
131
+
132
+ let stdout = '';
133
+ let stderr = '';
134
+
135
+ proc.stdout?.on('data', (data) => { stdout += data.toString(); });
136
+ proc.stderr?.on('data', (data) => { stderr += data.toString(); });
137
+
138
+ proc.on('close', (code) => {
139
+ if (code === 0) {
140
+ resolve({ stdout, stderr });
141
+ } else {
142
+ reject(new Error(stderr || `Python terminó con código ${code}`));
143
+ }
144
+ });
145
+
146
+ proc.on('error', (err) => {
147
+ reject(new Error(`No se pudo ejecutar Python: ${err.message}`));
148
+ });
149
+ });
150
+ }
@@ -13,7 +13,7 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
13
13
  /**
14
14
  * Retorna la ruta al directorio src/g360_core del módulo Python.
15
15
  */
16
- export function getPythonModulePath(): string {
16
+ export function getPythonModulePath() {
17
17
  // Ruta de desarrollo: py/src/g360_core relativo a este archivo
18
18
  const devPath = path.join(__dirname, '..', 'py', 'src');
19
19
  if (fs.existsSync(devPath)) {
@@ -39,7 +39,7 @@ export function getPythonModulePath(): string {
39
39
  /**
40
40
  * Genera código Python que configura sys.path correctamente.
41
41
  */
42
- export function wrapPythonCode(pyCode: string): string {
42
+ export function wrapPythonCode(pyCode) {
43
43
  const modulePath = getPythonModulePath().replace(/\\/g, '\\\\');
44
44
 
45
45
  return `
@@ -58,7 +58,7 @@ ${pyCode}
58
58
  /**
59
59
  * Ejecuta código Python y retorna su salida.
60
60
  */
61
- export async function runPython(pyCode: string): Promise<{ stdout: string; stderr: string }> {
61
+ export async function runPython(pyCode) {
62
62
  const fullCode = wrapPythonCode(pyCode);
63
63
  const pyExec = process.env.PYTHON || 'python3';
64
64
 
@@ -71,19 +71,19 @@ export async function runPython(pyCode: string): Promise<{ stdout: string; stder
71
71
  let stdout = '';
72
72
  let stderr = '';
73
73
 
74
- await new Promise<void>((resolve, reject) => {
75
- proc.stdout?.on('data', (data: Buffer) => { stdout += data.toString(); });
76
- proc.stderr?.on('data', (data: Buffer) => { stderr += data.toString(); });
74
+ await new Promise((resolve, reject) => {
75
+ proc.stdout?.on('data', (data) => { stdout += data.toString(); });
76
+ proc.stderr?.on('data', (data) => { stderr += data.toString(); });
77
77
 
78
78
  proc.on('close', (code) => {
79
79
  if (code === 0) resolve();
80
80
  else reject(new Error(stderr || `Python código ${code}`));
81
81
  });
82
82
 
83
- proc.on('error', (err: Error) => {
83
+ proc.on('error', (err) => {
84
84
  reject(new Error(`Python no disponible: ${err.message}`));
85
85
  });
86
86
  });
87
87
 
88
88
  return { stdout, stderr };
89
- }
89
+ }
@@ -1,44 +0,0 @@
1
- export function validate(data, rules) {
2
- const results = {
3
- valid: true,
4
- errors: [],
5
- warnings: []
6
- };
7
-
8
- for (const [field, rule] of Object.entries(rules)) {
9
- const value = data[field];
10
-
11
- if (rule.required && (value === undefined || value === null || value === '')) {
12
- results.errors.push({ field, message: `${field} is required` });
13
- results.valid = false;
14
- continue;
15
- }
16
-
17
- if (rule.type && value !== undefined) {
18
- const actualType = Array.isArray(value) ? 'array' : typeof value;
19
- if (actualType !== rule.type) {
20
- results.errors.push({ field, message: `${field} should be ${rule.type}` });
21
- results.valid = false;
22
- }
23
- }
24
-
25
- if (rule.min !== undefined && typeof value === 'number' && value < rule.min) {
26
- results.errors.push({ field, message: `${field} must be >= ${rule.min}` });
27
- results.valid = false;
28
- }
29
-
30
- if (rule.max !== undefined && typeof value === 'number' && value > rule.max) {
31
- results.errors.push({ field, message: `${field} must be <= ${rule.max}` });
32
- results.valid = false;
33
- }
34
-
35
- if (rule.pattern && typeof value === 'string' && !rule.pattern.test(value)) {
36
- results.errors.push({ field, message: `${field} format is invalid` });
37
- results.valid = false;
38
- }
39
- }
40
-
41
- return results;
42
- }
43
-
44
- export default { validate };
@@ -1,12 +0,0 @@
1
- const G360 = {
2
- name: 'G360 Ecosystem',
3
- version: '1.0.0',
4
- config: {
5
- theme: 'cool-light',
6
- primaryColor: '#3B82F6',
7
- secondaryColor: '#10B981',
8
- accentColor: '#8B5CF6'
9
- }
10
- };
11
-
12
- export default G360;
@@ -1,35 +0,0 @@
1
- export function mapField(sourceField, fieldMap) {
2
- const mapping = fieldMap[sourceField];
3
- if (!mapping) {
4
- return {
5
- success: false,
6
- targetField: null,
7
- message: `No mapping found for: ${sourceField}`
8
- };
9
- }
10
- return {
11
- success: true,
12
- targetField: mapping.target,
13
- transformations: mapping.transform || []
14
- };
15
- }
16
-
17
- export function validateMapping(sourceData, fieldMap) {
18
- const results = {
19
- valid: true,
20
- errors: [],
21
- warnings: []
22
- };
23
-
24
- for (const sourceField of Object.keys(sourceData)) {
25
- const mapping = mapField(sourceField, fieldMap);
26
- if (!mapping.success) {
27
- results.warnings.push(mapping.message);
28
- }
29
- }
30
-
31
- results.valid = results.errors.length === 0;
32
- return results;
33
- }
34
-
35
- export default { mapField, validateMapping };
@@ -1,37 +0,0 @@
1
- export async function audit(code, options = {}) {
2
- const results = {
3
- score: 0,
4
- issues: [],
5
- suggestions: []
6
- };
7
-
8
- if (!code || code.length === 0) {
9
- results.issues.push({ line: 0, message: 'Empty code' });
10
- return results;
11
- }
12
-
13
- const lines = code.split('\n');
14
- let score = 100;
15
-
16
- lines.forEach((line, index) => {
17
- if (line.length > 120) {
18
- results.issues.push({ line: index + 1, message: 'Line too long (>120 chars)' });
19
- score -= 2;
20
- }
21
-
22
- if (line.includes('TODO') || line.includes('FIXME')) {
23
- results.suggestions.push({ line: index + 1, message: 'Unresolved TODO/FIXME' });
24
- score -= 1;
25
- }
26
- });
27
-
28
- if (!code.includes('g360') && !code.includes('G360')) {
29
- results.suggestions.push({ line: 0, message: 'Consider adding G360 identity' });
30
- score -= 5;
31
- }
32
-
33
- results.score = Math.max(0, score);
34
- return results;
35
- }
36
-
37
- export default { audit };
@@ -1,33 +0,0 @@
1
- export async function evaluateMetaTags(html) {
2
- const results = {
3
- score: 0,
4
- tags: [],
5
- issues: [],
6
- suggestions: []
7
- };
8
-
9
- const requiredTags = ['title', 'description', 'viewport'];
10
- const metaTags = html.match(/<meta[^>]+>/gi) || [];
11
-
12
- requiredTags.forEach(tag => {
13
- if (html.includes(`name="${tag}"`) || html.includes(`property="${tag}"`)) {
14
- results.tags.push({ tag, found: true });
15
- } else {
16
- results.issues.push({ tag, message: `Missing ${tag} tag` });
17
- }
18
- });
19
-
20
- const ogTags = ['og:title', 'og:description', 'og:image'];
21
- ogTags.forEach(tag => {
22
- if (html.includes(`property="${tag}"`)) {
23
- results.tags.push({ tag, found: true, type: 'og' });
24
- } else {
25
- results.suggestions.push({ tag, message: `Consider adding Open Graph: ${tag}` });
26
- }
27
- });
28
-
29
- results.score = Math.round((results.tags.length / (requiredTags.length + ogTags.length)) * 100);
30
- return results;
31
- }
32
-
33
- export default { evaluateMetaTags };
package/src/lib/assets.js DELETED
@@ -1,38 +0,0 @@
1
- import fs from 'fs-extra';
2
- import path from 'path';
3
- import { fileURLToPath } from 'url';
4
-
5
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
6
-
7
- export const assets = {
8
- path: path.join(__dirname, '../assets'),
9
-
10
- exists(assetPath) {
11
- return fs.existsSync(path.join(this.path, assetPath));
12
- },
13
-
14
- async copy(assetPath, destPath, options = {}) {
15
- const { overwrite = false } = options;
16
- const src = path.join(this.path, assetPath);
17
- const dest = path.join(destPath, path.basename(assetPath));
18
-
19
- if (!this.exists(assetPath)) {
20
- throw new Error(`Asset not found: ${assetPath}`);
21
- }
22
-
23
- if (fs.existsSync(dest) && !overwrite) {
24
- throw new Error(`Destination already exists: ${dest}`);
25
- }
26
-
27
- await fs.copy(src, dest, { overwrite });
28
- return dest;
29
- },
30
-
31
- list(category) {
32
- const categoryPath = path.join(this.path, category);
33
- if (!fs.existsSync(categoryPath)) {
34
- return [];
35
- }
36
- return fs.readdirSync(categoryPath);
37
- }
38
- };
@@ -1,27 +0,0 @@
1
- import fs from 'fs-extra';
2
- import crypto from 'crypto';
3
-
4
- export const checksum = {
5
- async calculate(filePath) {
6
- const content = await fs.readFile(filePath);
7
- return crypto.createHash('md5').update(content).digest('hex');
8
- },
9
-
10
- async verify(filePath, expectedHash) {
11
- const actualHash = await this.calculate(filePath);
12
- return actualHash === expectedHash;
13
- },
14
-
15
- async generateManifest(dir, files = []) {
16
- const manifest = {};
17
-
18
- for (const file of files) {
19
- const filePath = `${dir}/${file}`;
20
- if (fs.existsSync(filePath)) {
21
- manifest[file] = await this.calculate(filePath);
22
- }
23
- }
24
-
25
- return manifest;
26
- }
27
- };
package/src/lib/config.js DELETED
@@ -1,23 +0,0 @@
1
- export const config = {
2
- defaults: {
3
- template: 'web-pwa',
4
- assets: ['components', 'skills', 'engine'],
5
- theme: 'cool-light'
6
- },
7
-
8
- projectTypes: {
9
- 'web-pwa': { framework: 'vanilla', features: ['pwa', 'offline'] },
10
- 'web-svelte': { framework: 'svelte', features: ['routing', 'stores'] },
11
- 'python-cli': { framework: 'python', features: ['cli', 'argparse'] },
12
- 'vba-excel': { framework: 'vba', features: ['excel', 'macros'] }
13
- },
14
-
15
- themes: {
16
- 'cool-light': { primary: '#3B82F6', secondary: '#10B981', accent: '#8B5CF6' },
17
- 'cool-dark': { primary: '#60A5FA', secondary: '#34D399', accent: '#A78BFA' },
18
- 'warm-light': { primary: '#F59E0B', secondary: '#EF4444', accent: '#8B5CF6' },
19
- 'warm-dark': { primary: '#FBBF24', secondary: '#F87171', accent: '#A78BFA' },
20
- 'neutral-light': { primary: '#6B7280', secondary: '#374151', accent: '#111827' },
21
- 'neutral-dark': { primary: '#9CA3AF', secondary: '#D1D5DB', accent: '#F9FAFB' }
22
- }
23
- };
@@ -1,33 +0,0 @@
1
- import fs from 'fs-extra';
2
- import path from 'path';
3
-
4
- export const offline = {
5
- cache: new Map(),
6
- cacheDir: '.g360-cache',
7
-
8
- async isAvailable() {
9
- return true;
10
- },
11
-
12
- async getCached(asset) {
13
- return this.cache.get(asset);
14
- },
15
-
16
- async setCache(asset, data) {
17
- this.cache.set(asset, data);
18
- },
19
-
20
- async loadFromCache(asset) {
21
- const cachePath = path.join(this.cacheDir, `${asset}.json`);
22
- if (fs.existsSync(cachePath)) {
23
- return fs.readJson(cachePath);
24
- }
25
- return null;
26
- },
27
-
28
- async saveToCache(asset, data) {
29
- const cachePath = path.join(this.cacheDir, `${asset}.json`);
30
- await fs.ensureDir(this.cacheDir);
31
- await fs.writeJson(cachePath, data);
32
- }
33
- };
@@ -1,24 +0,0 @@
1
- import chalk from 'chalk';
2
-
3
- export const presenter = {
4
- formatTree(items, prefix = '', isLast = true) {
5
- return items.map((item, index) => {
6
- const isLastItem = index === items.length - 1;
7
- const connector = isLastItem ? '└── ' : '├── ';
8
- return `${prefix}${connector}${item}`;
9
- }).join('\n');
10
- },
11
-
12
- formatList(items, columns = 2) {
13
- const rows = [];
14
- for (let i = 0; i < items.length; i += columns) {
15
- const row = items.slice(i, i + columns);
16
- rows.push(row.join('\t'));
17
- }
18
- return rows.join('\n');
19
- },
20
-
21
- formatJson(data) {
22
- return JSON.stringify(data, null, 2);
23
- }
24
- };
@@ -1,49 +0,0 @@
1
- import fs from 'fs-extra';
2
- import path from 'path';
3
-
4
- export const rollback = {
5
- history: new Map(),
6
-
7
- async snapshot(projectDir, label) {
8
- const snapshot = {
9
- timestamp: new Date().toISOString(),
10
- label,
11
- files: []
12
- };
13
-
14
- const g360Dir = path.join(projectDir, 'g360');
15
- if (fs.existsSync(g360Dir)) {
16
- const files = fs.readdirSync(g360Dir, { recursive: true, withFileTypes: true });
17
- snapshot.files = files.map(f => ({
18
- path: f.fullPath || path.join(g360Dir, f.name),
19
- type: f.isDirectory() ? 'dir' : 'file'
20
- }));
21
- }
22
-
23
- this.history.set(projectDir, snapshot);
24
- return snapshot;
25
- },
26
-
27
- async restore(projectDir) {
28
- const snapshot = this.history.get(projectDir);
29
- if (!snapshot) {
30
- throw new Error('No snapshot found for this project');
31
- }
32
-
33
- const g360Dir = path.join(projectDir, 'g360');
34
-
35
- if (fs.existsSync(g360Dir)) {
36
- await fs.remove(g360Dir);
37
- }
38
-
39
- for (const file of snapshot.files) {
40
- if (file.type === 'dir') {
41
- await fs.ensureDir(file.path);
42
- } else {
43
- await fs.ensureFile(file.path);
44
- }
45
- }
46
-
47
- return snapshot;
48
- }
49
- };
package/src/lib/theme.js DELETED
@@ -1,30 +0,0 @@
1
- import chalk from 'chalk';
2
-
3
- export const theme = {
4
- colors: {
5
- primary: '#3B82F6',
6
- secondary: '#10B981',
7
- accent: '#8B5CF6',
8
- success: '#22C55E',
9
- warning: '#F59E0B',
10
- error: '#EF4444',
11
- info: '#06B6D4'
12
- },
13
-
14
- styles: {
15
- header: chalk.bold.cyan,
16
- success: chalk.green,
17
- error: chalk.red,
18
- warning: chalk.yellow,
19
- info: chalk.blue,
20
- muted: chalk.gray
21
- },
22
-
23
- format: {
24
- title: (text) => chalk.bold.cyan(`\n${text}\n`),
25
- section: (text) => chalk.bold.yellow(text),
26
- item: (text) => chalk.white(text),
27
- key: (text) => chalk.cyan(text),
28
- value: (text) => chalk.white(text)
29
- }
30
- };