g360-cli 1.14.0 → 1.15.1
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 +72 -6
- package/package.json +71 -71
- package/src/assets/config/g360-skills.json +126 -0
- package/src/assets/config/project-types.json +8 -0
- package/src/assets/snippets/snippets.json +4 -0
- package/src/assets/templates/lit-web/src/index.js +1 -1
- package/src/assets/templates/python-cli/src/core/__init__.py +0 -0
- package/src/assets/templates/python-flet/pyproject.toml +1 -0
- package/src/assets/templates/python-flet/src/core/skill.json +1 -1
- package/src/assets/templates/python-flet-migrate/pyproject.toml +1 -3
- package/src/assets/templates/python-flet-polished/src/core/skill.json +16 -17
- package/src/assets/templates/solid-web/src/components/App.jsx +2 -2
- package/src/commands/clean.js +0 -12
- package/src/commands/docs.js +105 -1
- package/src/commands/ingest.js +11 -61
- package/src/commands/init.js +1 -1
- package/src/commands/lint.js +35 -77
- package/src/commands/scan.js +9 -37
- package/src/commands/validate.js +6 -36
- package/src/lib/file-utils.js +89 -2
- package/src/lib/python-runner.js +77 -0
- package/src/assets/templates/python-cli/package.json +0 -7
- /package/src/assets/config/{skills.json → agent-skills.json} +0 -0
package/src/commands/docs.js
CHANGED
|
@@ -4,7 +4,7 @@ import path from 'path';
|
|
|
4
4
|
import { fileURLToPath } from 'url';
|
|
5
5
|
|
|
6
6
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
-
const BRAND_PATH = path.join(__dirname, '..', '
|
|
7
|
+
const BRAND_PATH = path.join(__dirname, '..', 'assets', 'brand', 'brand.json');
|
|
8
8
|
|
|
9
9
|
const LEVELS = ['readme', 'architecture', 'business-rules', 'api', 'dependencies', 'classes', 'code-graph', 'all'];
|
|
10
10
|
|
|
@@ -176,6 +176,7 @@ async function generateLevel(lvl, dir, projectInfo, brand, manifest, skill, dryR
|
|
|
176
176
|
case 'dependencies': return await generateDependencies(dir, projectInfo, dryRun);
|
|
177
177
|
case 'classes': return await generateClasses(dir, projectInfo, dryRun);
|
|
178
178
|
case 'code-graph': return await generateCodeGraph(dir, projectInfo, dryRun);
|
|
179
|
+
case 'api': return await generateApi(dir, projectInfo, dryRun);
|
|
179
180
|
default: return null;
|
|
180
181
|
}
|
|
181
182
|
}
|
|
@@ -537,6 +538,109 @@ async function generateCodeGraph(dir, projectInfo, dryRun) {
|
|
|
537
538
|
return 'docs/generated/code_graph.mmd';
|
|
538
539
|
}
|
|
539
540
|
|
|
541
|
+
async function generateApi(dir, projectInfo, dryRun) {
|
|
542
|
+
const scan = scanProject(dir);
|
|
543
|
+
const pyFiles = scan.pyFiles.filter(f => !f.endsWith('__init__.py'));
|
|
544
|
+
const jsFiles = scan.jsFiles.filter(f => !f.endsWith('.test.js') && !f.endsWith('.spec.js'));
|
|
545
|
+
|
|
546
|
+
const exportedFunctions = [];
|
|
547
|
+
const exportedClasses = [];
|
|
548
|
+
|
|
549
|
+
// Python: detectar def y class exportados
|
|
550
|
+
for (const file of pyFiles) {
|
|
551
|
+
const filePath = path.join(dir, file);
|
|
552
|
+
try {
|
|
553
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
554
|
+
const funcMatches = content.match(/^(?:def|async\s+def)\s+(\w+)\s*\(/gm);
|
|
555
|
+
if (funcMatches) {
|
|
556
|
+
for (const m of funcMatches) {
|
|
557
|
+
const name = m.replace(/^(?:async\s+)?def\s+/, '').replace(/\s*\(.*/, '');
|
|
558
|
+
if (!name.startsWith('_')) {
|
|
559
|
+
exportedFunctions.push({ file, name, type: 'python' });
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
const classMatches = content.match(/^(?:class)\s+(\w+)/gm);
|
|
564
|
+
if (classMatches) {
|
|
565
|
+
for (const m of classMatches) {
|
|
566
|
+
const name = m.replace(/class\s+/, '').replace(/\s*[:(\{].*/, '');
|
|
567
|
+
if (!name.startsWith('_')) {
|
|
568
|
+
exportedClasses.push({ file, name, type: 'python' });
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
} catch { /* skip unreadable */ }
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
// JS/TS: detectar export function y export class
|
|
576
|
+
for (const file of jsFiles) {
|
|
577
|
+
const filePath = path.join(dir, file);
|
|
578
|
+
try {
|
|
579
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
580
|
+
const funcMatches = content.match(/^export\s+(?:async\s+)?function\s+(\w+)/gm);
|
|
581
|
+
if (funcMatches) {
|
|
582
|
+
for (const m of funcMatches) {
|
|
583
|
+
const name = m.replace(/^export\s+(?:async\s+)?function\s+/, '');
|
|
584
|
+
exportedFunctions.push({ file, name, type: 'javascript' });
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
const classMatches = content.match(/^export\s+class\s+(\w+)/gm);
|
|
588
|
+
if (classMatches) {
|
|
589
|
+
for (const m of classMatches) {
|
|
590
|
+
const name = m.replace(/^export\s+class\s+/, '');
|
|
591
|
+
exportedClasses.push({ file, name, type: 'javascript' });
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
} catch { /* skip unreadable */ }
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
if (exportedFunctions.length === 0 && exportedClasses.length === 0) {
|
|
598
|
+
console.log(chalk.gray(' ⚠ No se encontraron exports publicos. Generando API basica.'));
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
// Generar markdown
|
|
602
|
+
let md = `# API Reference\n\n`;
|
|
603
|
+
md += `> Generado automaticamente por \`g360 docs api\`\n\n`;
|
|
604
|
+
|
|
605
|
+
if (exportedClasses.length > 0) {
|
|
606
|
+
md += `## Clases\n\n`;
|
|
607
|
+
md += `| Clase | Archivo | Tipo |\n`;
|
|
608
|
+
md += `|-------|---------|------|\n`;
|
|
609
|
+
for (const c of exportedClasses) {
|
|
610
|
+
md += `| \`${c.name}\` | \`${c.file}\` | ${c.type} |\n`;
|
|
611
|
+
}
|
|
612
|
+
md += `\n`;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
if (exportedFunctions.length > 0) {
|
|
616
|
+
md += `## Funciones\n\n`;
|
|
617
|
+
md += `| Funcion | Archivo | Tipo |\n`;
|
|
618
|
+
md += `|---------|---------|------|\n`;
|
|
619
|
+
for (const f of exportedFunctions) {
|
|
620
|
+
md += `| \`${f.name}\` | \`${f.file}\` | ${f.type} |\n`;
|
|
621
|
+
}
|
|
622
|
+
md += `\n`;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
if (exportedFunctions.length === 0 && exportedClasses.length === 0) {
|
|
626
|
+
md += `_No se encontraron exports publicos en el proyecto._\n`;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
const outputDir = path.join(dir, 'docs', 'generated');
|
|
630
|
+
const outputPath = path.join(outputDir, 'api.md');
|
|
631
|
+
|
|
632
|
+
if (dryRun) {
|
|
633
|
+
console.log(chalk.gray(` [dry-run] docs/generated/api.md`));
|
|
634
|
+
console.log(chalk.gray(` Funciones: ${exportedFunctions.length} | Clases: ${exportedClasses.length}`));
|
|
635
|
+
return null;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
await fs.ensureDir(outputDir);
|
|
639
|
+
await fs.writeFile(outputPath, md, 'utf8');
|
|
640
|
+
console.log(chalk.green(` ✅ docs/generated/api.md (${exportedFunctions.length} funciones, ${exportedClasses.length} clases)`));
|
|
641
|
+
return 'docs/generated/api.md';
|
|
642
|
+
}
|
|
643
|
+
|
|
540
644
|
function scanProject(dir) {
|
|
541
645
|
const results = {
|
|
542
646
|
files: [],
|
package/src/commands/ingest.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import chalk from 'chalk';
|
|
8
8
|
import path from 'path';
|
|
9
9
|
import { fileURLToPath } from 'url';
|
|
10
|
-
import {
|
|
10
|
+
import { runPython, runPythonStdout, g360CorePath } from '../lib/python-runner.js';
|
|
11
11
|
import fs from 'fs-extra';
|
|
12
12
|
|
|
13
13
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -38,12 +38,12 @@ export async function ingest(input, options) {
|
|
|
38
38
|
const result = await scanDirectory(inputPath);
|
|
39
39
|
filepaths = result.valid.map(info => info.path);
|
|
40
40
|
if (filepaths.length === 0) {
|
|
41
|
-
console.error(chalk.red('❌ No se encontraron archivos ERP
|
|
41
|
+
console.error(chalk.red('❌ No se encontraron archivos ERP validos en el directorio'));
|
|
42
42
|
process.exit(1);
|
|
43
43
|
}
|
|
44
|
-
console.log(chalk.green(` Encontrados ${filepaths.length} archivos
|
|
44
|
+
console.log(chalk.green(` Encontrados ${filepaths.length} archivos validos`));
|
|
45
45
|
} else {
|
|
46
|
-
console.error(chalk.red(`❌ Ruta no
|
|
46
|
+
console.error(chalk.red(`❌ Ruta no valida: ${inputPath}`));
|
|
47
47
|
process.exit(1);
|
|
48
48
|
}
|
|
49
49
|
|
|
@@ -90,8 +90,7 @@ export async function ingest(input, options) {
|
|
|
90
90
|
|
|
91
91
|
async function scanDirectory(dir) {
|
|
92
92
|
const pyCode = `
|
|
93
|
-
|
|
94
|
-
sys.path.insert(0, '${path.join(__dirname, '..', 'py', 'src')}')
|
|
93
|
+
${g360CorePath(__dirname)}
|
|
95
94
|
from g360_core.scanner import find_erp_files_in_dir
|
|
96
95
|
from pathlib import Path
|
|
97
96
|
|
|
@@ -102,8 +101,8 @@ for i in invalid[:10]:
|
|
|
102
101
|
print(f"INVALID::{i.path.name}::${i.error_msg}")
|
|
103
102
|
`;
|
|
104
103
|
|
|
105
|
-
const
|
|
106
|
-
const lines =
|
|
104
|
+
const { stdout } = await runPython(pyCode);
|
|
105
|
+
const lines = stdout.split('\n').filter(l => l.trim());
|
|
107
106
|
const valid = [];
|
|
108
107
|
const invalid = [];
|
|
109
108
|
|
|
@@ -122,66 +121,17 @@ for i in invalid[:10]:
|
|
|
122
121
|
|
|
123
122
|
async function runBatchIngest(filepaths) {
|
|
124
123
|
const pyCode = `
|
|
125
|
-
|
|
126
|
-
sys.path.insert(0, '${path.join(__dirname, '..', 'py', 'src')}')
|
|
124
|
+
${g360CorePath(__dirname)}
|
|
127
125
|
from g360_core.scanner import batch_process_files
|
|
128
126
|
from pathlib import Path
|
|
129
127
|
import pandas as pd
|
|
128
|
+
import sys
|
|
130
129
|
|
|
131
130
|
filepaths = [${JSON.stringify(filepaths).replace(/"/g, "'")}]
|
|
132
131
|
df = batch_process_files([Path(p) for p in filepaths], merge_results=True)
|
|
133
132
|
sys.stdout.write(df.to_csv(index=False))
|
|
134
133
|
`;
|
|
135
134
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
const proc = spawn(pyExec, ['-c', pyCode], {
|
|
139
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
let stdout = '';
|
|
143
|
-
let stderr = '';
|
|
144
|
-
|
|
145
|
-
proc.stdout?.on('data', (data) => { stdout += data.toString(); });
|
|
146
|
-
proc.stderr?.on('data', (data) => { stderr += data.toString(); });
|
|
147
|
-
|
|
148
|
-
proc.on('close', (code) => {
|
|
149
|
-
if (code === 0) {
|
|
150
|
-
resolve(stdout);
|
|
151
|
-
} else {
|
|
152
|
-
reject(new Error(stderr || `Python terminó con código ${code}`));
|
|
153
|
-
}
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
proc.on('error', (err) => {
|
|
157
|
-
reject(new Error(`No se pudo ejecutar Python: ${err.message}`));
|
|
158
|
-
});
|
|
159
|
-
});
|
|
135
|
+
const { stdout } = await runPython(pyCode);
|
|
136
|
+
return stdout;
|
|
160
137
|
}
|
|
161
|
-
|
|
162
|
-
function runPython(code) {
|
|
163
|
-
return new Promise((resolve, reject) => {
|
|
164
|
-
const pyExec = process.env.PYTHON || 'python3';
|
|
165
|
-
const proc = spawn(pyExec, ['-c', code], {
|
|
166
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
let stdout = '';
|
|
170
|
-
let stderr = '';
|
|
171
|
-
|
|
172
|
-
proc.stdout?.on('data', (data) => { stdout += data.toString(); });
|
|
173
|
-
proc.stderr?.on('data', (data) => { stderr += data.toString(); });
|
|
174
|
-
|
|
175
|
-
proc.on('close', (code) => {
|
|
176
|
-
if (code === 0) {
|
|
177
|
-
resolve({ stdout, stderr });
|
|
178
|
-
} else {
|
|
179
|
-
reject(new Error(stderr || `Python terminó con código ${code}`));
|
|
180
|
-
}
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
proc.on('error', (err) => {
|
|
184
|
-
reject(new Error(`No se pudo ejecutar Python: ${err.message}`));
|
|
185
|
-
});
|
|
186
|
-
});
|
|
187
|
-
}
|
package/src/commands/init.js
CHANGED
|
@@ -222,7 +222,7 @@ async function createG360Structure(projectPath, assetsDir) {
|
|
|
222
222
|
|
|
223
223
|
const skillJsonPath = path.join(g360Dir, 'skill.json');
|
|
224
224
|
if (!fs.existsSync(skillJsonPath)) {
|
|
225
|
-
const exampleSkillPath = path.join(assetsDir, 'config/skills.json');
|
|
225
|
+
const exampleSkillPath = path.join(assetsDir, 'config/agent-skills.json');
|
|
226
226
|
if (fs.existsSync(exampleSkillPath)) {
|
|
227
227
|
await fs.copy(exampleSkillPath, skillJsonPath);
|
|
228
228
|
}
|
package/src/commands/lint.js
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import fs from 'fs-extra';
|
|
3
3
|
import path from 'path';
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
4
|
+
import { walkProject } from '../lib/file-utils.js';
|
|
7
5
|
|
|
8
6
|
const SEVERITY = {
|
|
9
7
|
CRITICAL: 'critical',
|
|
@@ -30,8 +28,8 @@ const PYTHON_NAMING_RULES = {
|
|
|
30
28
|
const GENERIC_NAMES = ['result', 'data', 'info', 'val', 'obj', 'tmp', 'aux', 'value', 'x', 'y', 'z', 'item', 'elem', 'entry', 'output', 'input', 'res', 'dt'];
|
|
31
29
|
|
|
32
30
|
export async function lint(targetPath, options) {
|
|
33
|
-
const {
|
|
34
|
-
const targetDir = path.join(process.cwd(), project);
|
|
31
|
+
const { level = 'all', project } = options;
|
|
32
|
+
const targetDir = project ? path.join(process.cwd(), project) : path.resolve(targetPath || '.');
|
|
35
33
|
|
|
36
34
|
if (!fs.existsSync(targetDir)) {
|
|
37
35
|
console.error(chalk.red(`❌ Directorio no encontrado: ${targetDir}`));
|
|
@@ -93,30 +91,10 @@ function checkNamingConventions(dir) {
|
|
|
93
91
|
const jsFiles = [];
|
|
94
92
|
const pyFiles = [];
|
|
95
93
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
if (item.name === 'node_modules') continue;
|
|
101
|
-
if (item.name === '__pycache__') continue;
|
|
102
|
-
if (item.name === '.pytest_cache') continue;
|
|
103
|
-
if (item.name === 'g360') continue;
|
|
104
|
-
|
|
105
|
-
const fullPath = path.join(d, item.name);
|
|
106
|
-
if (item.isDirectory()) {
|
|
107
|
-
walk(fullPath);
|
|
108
|
-
} else if (item.isFile()) {
|
|
109
|
-
if (item.name.endsWith('.js') && !item.name.endsWith('.test.js')) {
|
|
110
|
-
jsFiles.push(fullPath);
|
|
111
|
-
}
|
|
112
|
-
if (item.name.endsWith('.py') && !item.name.endsWith('__pycache__')) {
|
|
113
|
-
pyFiles.push(fullPath);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
walk(dir);
|
|
94
|
+
walkProject(dir, {
|
|
95
|
+
onJs: (fullPath) => jsFiles.push(fullPath),
|
|
96
|
+
onPy: (fullPath) => pyFiles.push(fullPath),
|
|
97
|
+
});
|
|
120
98
|
|
|
121
99
|
for (const file of jsFiles) {
|
|
122
100
|
findings.push(...checkJsFileNaming(file, dir));
|
|
@@ -271,30 +249,10 @@ function checkDuplicateFunctions(dir) {
|
|
|
271
249
|
const findings = [];
|
|
272
250
|
const functionMap = new Map();
|
|
273
251
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
if (item.name === 'node_modules') continue;
|
|
279
|
-
if (item.name === '__pycache__') continue;
|
|
280
|
-
if (item.name === '.pytest_cache') continue;
|
|
281
|
-
if (item.name === 'g360') continue;
|
|
282
|
-
|
|
283
|
-
const fullPath = path.join(d, item.name);
|
|
284
|
-
if (item.isDirectory()) {
|
|
285
|
-
walk(fullPath);
|
|
286
|
-
} else if (item.isFile()) {
|
|
287
|
-
if (item.name.endsWith('.js') && !item.name.endsWith('.test.js')) {
|
|
288
|
-
extractFunctions(fullPath, 'js', functionMap);
|
|
289
|
-
}
|
|
290
|
-
if (item.name.endsWith('.py') && !item.name.endsWith('__pycache__')) {
|
|
291
|
-
extractFunctions(fullPath, 'py', functionMap);
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
walk(dir);
|
|
252
|
+
walkProject(dir, {
|
|
253
|
+
onJs: (fullPath) => extractFunctions(fullPath, 'js', functionMap),
|
|
254
|
+
onPy: (fullPath) => extractFunctions(fullPath, 'py', functionMap),
|
|
255
|
+
});
|
|
298
256
|
|
|
299
257
|
for (const [name, locations] of functionMap) {
|
|
300
258
|
if (locations.length > 1) {
|
|
@@ -411,37 +369,23 @@ function extractFunctions(filePath, lang, functionMap) {
|
|
|
411
369
|
function checkSyntaxErrors(dir) {
|
|
412
370
|
const findings = [];
|
|
413
371
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
if (item.name === 'node_modules') continue;
|
|
419
|
-
if (item.name === '__pycache__') continue;
|
|
420
|
-
if (item.name === '.pytest_cache') continue;
|
|
421
|
-
if (item.name === 'g360') continue;
|
|
422
|
-
|
|
423
|
-
const fullPath = path.join(d, item.name);
|
|
424
|
-
if (item.isDirectory()) {
|
|
425
|
-
walk(fullPath);
|
|
426
|
-
} else if (item.isFile()) {
|
|
427
|
-
if (item.name.endsWith('.js') && !item.name.endsWith('.test.js')) {
|
|
428
|
-
checkJsSyntax(fullPath, dir, findings);
|
|
429
|
-
}
|
|
430
|
-
if (item.name.endsWith('.py')) {
|
|
431
|
-
checkPySyntax(fullPath, dir, findings);
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
}
|
|
372
|
+
walkProject(dir, {
|
|
373
|
+
onJs: (fullPath) => checkJsSyntax(fullPath, dir, findings),
|
|
374
|
+
onPy: (fullPath) => checkPySyntax(fullPath, dir, findings),
|
|
375
|
+
});
|
|
436
376
|
|
|
437
|
-
walk(dir);
|
|
438
377
|
return findings;
|
|
439
378
|
}
|
|
440
379
|
|
|
441
380
|
function checkJsSyntax(filePath, projectDir, findings) {
|
|
442
381
|
const relPath = path.relative(projectDir, filePath);
|
|
382
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
383
|
+
|
|
384
|
+
// Skip ES modules (import/export) — new Function() no puede parsearlos
|
|
385
|
+
if (/^(?:import|export)\s/m.test(content)) return;
|
|
386
|
+
|
|
443
387
|
try {
|
|
444
|
-
new Function(
|
|
388
|
+
new Function(content);
|
|
445
389
|
} catch (error) {
|
|
446
390
|
findings.push({
|
|
447
391
|
severity: SEVERITY.CRITICAL,
|
|
@@ -492,6 +436,20 @@ function checkPySyntax(filePath, projectDir, findings) {
|
|
|
492
436
|
while (indentStack.length > 1 && indentStack[indentStack.length - 1] > indent) {
|
|
493
437
|
indentStack.pop();
|
|
494
438
|
}
|
|
439
|
+
// Verificar que la indentacion actual este en el stack
|
|
440
|
+
if (indentStack.length > 0 && indent !== indentStack[indentStack.length - 1]) {
|
|
441
|
+
const indentUnit = indentStack.length > 1 ? indentStack[1] - indentStack[0] : 4;
|
|
442
|
+
if (indent % indentUnit !== 0) {
|
|
443
|
+
findings.push({
|
|
444
|
+
severity: SEVERITY.WARNING,
|
|
445
|
+
file: relPath,
|
|
446
|
+
type: 'indentation-error',
|
|
447
|
+
message: `Indentacion inconsistente en linea ${i + 1}: ${indent} espacios (esperado multiplo de ${indentUnit})`,
|
|
448
|
+
current: `${indent} espacios`,
|
|
449
|
+
recommended: `Multiplo de ${indentUnit} espacios`,
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
}
|
|
495
453
|
}
|
|
496
454
|
}
|
|
497
455
|
}
|
package/src/commands/scan.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
3
|
* Comando: g360 scan <directorio>
|
|
4
|
-
* Escanea un directorio para detectar archivos ERP
|
|
4
|
+
* Escanea un directorio para detectar archivos ERP validos.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import chalk from 'chalk';
|
|
8
8
|
import path from 'path';
|
|
9
9
|
import { fileURLToPath } from 'url';
|
|
10
|
-
import {
|
|
10
|
+
import { runPythonStdout, g360CorePath } from '../lib/python-runner.js';
|
|
11
11
|
|
|
12
12
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
13
13
|
|
|
@@ -17,8 +17,7 @@ export async function scan(directory, options) {
|
|
|
17
17
|
console.log(chalk.blue(`\n🔍 Escaneando directorio: ${directory}`));
|
|
18
18
|
|
|
19
19
|
const pyCode = `
|
|
20
|
-
|
|
21
|
-
sys.path.insert(0, '${path.join(__dirname, '..', 'py', 'src')}')
|
|
20
|
+
${g360CorePath(__dirname)}
|
|
22
21
|
from g360_core.scanner import find_erp_files_in_dir
|
|
23
22
|
from pathlib import Path
|
|
24
23
|
import json
|
|
@@ -33,22 +32,22 @@ print(json.dumps(result, indent=2))
|
|
|
33
32
|
`;
|
|
34
33
|
|
|
35
34
|
try {
|
|
36
|
-
const
|
|
37
|
-
const data = JSON.parse(
|
|
35
|
+
const stdout = await runPythonStdout(pyCode);
|
|
36
|
+
const data = JSON.parse(stdout);
|
|
38
37
|
|
|
39
38
|
console.log(chalk.gray(`\n Total archivos: ${data.stats.total}`));
|
|
40
|
-
console.log(chalk.green(`
|
|
41
|
-
console.log(chalk.red(`
|
|
39
|
+
console.log(chalk.green(` Validos: ${data.stats.valid}`));
|
|
40
|
+
console.log(chalk.red(` Invalidos: ${data.stats.invalid}`));
|
|
42
41
|
|
|
43
42
|
if (data.valid.length > 0) {
|
|
44
|
-
console.log(chalk.cyan('\n📋 Archivos
|
|
43
|
+
console.log(chalk.cyan('\n📋 Archivos validos:'));
|
|
45
44
|
data.valid.forEach(f => {
|
|
46
45
|
console.log(chalk.gray(` ${f.path} (${f.erp_type})`));
|
|
47
46
|
});
|
|
48
47
|
}
|
|
49
48
|
|
|
50
49
|
if (data.invalid.length > 0) {
|
|
51
|
-
console.log(chalk.yellow('\n⚠️ Archivos
|
|
50
|
+
console.log(chalk.yellow('\n⚠️ Archivos invalidos:'));
|
|
52
51
|
data.invalid.forEach(f => {
|
|
53
52
|
console.log(chalk.red(` ${f.path}: ${f.error_msg}`));
|
|
54
53
|
});
|
|
@@ -61,30 +60,3 @@ print(json.dumps(result, indent=2))
|
|
|
61
60
|
process.exit(1);
|
|
62
61
|
}
|
|
63
62
|
}
|
|
64
|
-
|
|
65
|
-
function runPython(code) {
|
|
66
|
-
return new Promise((resolve, reject) => {
|
|
67
|
-
const pyExec = process.env.PYTHON || 'python3';
|
|
68
|
-
const proc = spawn(pyExec, ['-c', code], {
|
|
69
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
let stdout = '';
|
|
73
|
-
let stderr = '';
|
|
74
|
-
|
|
75
|
-
proc.stdout?.on('data', (data) => { stdout += data.toString(); });
|
|
76
|
-
proc.stderr?.on('data', (data) => { stderr += data.toString(); });
|
|
77
|
-
|
|
78
|
-
proc.on('close', (code) => {
|
|
79
|
-
if (code === 0) {
|
|
80
|
-
resolve({ stdout, stderr });
|
|
81
|
-
} else {
|
|
82
|
-
reject(new Error(stderr || `Python terminó con código ${code}`));
|
|
83
|
-
}
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
proc.on('error', (err) => {
|
|
87
|
-
reject(new Error(`No se pudo ejecutar Python: ${err.message}`));
|
|
88
|
-
});
|
|
89
|
-
});
|
|
90
|
-
}
|
package/src/commands/validate.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import chalk from 'chalk';
|
|
8
8
|
import path from 'path';
|
|
9
9
|
import { fileURLToPath } from 'url';
|
|
10
|
-
import {
|
|
10
|
+
import { runPython, g360CorePath } from '../lib/python-runner.js';
|
|
11
11
|
import fs from 'fs-extra';
|
|
12
12
|
|
|
13
13
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -27,7 +27,6 @@ export async function validate(paths, options) {
|
|
|
27
27
|
filesToCheck.push(p);
|
|
28
28
|
}
|
|
29
29
|
} else if (stat.isDirectory()) {
|
|
30
|
-
const pattern = recursive ? '**/*' : '*';
|
|
31
30
|
const extPattern = /\.(xls|xlsx|csv)$/i;
|
|
32
31
|
const entries = await fs.readdir(p, { withFileTypes: true });
|
|
33
32
|
for (const entry of entries) {
|
|
@@ -35,7 +34,6 @@ export async function validate(paths, options) {
|
|
|
35
34
|
if (entry.isFile() && extPattern.test(entry.name)) {
|
|
36
35
|
filesToCheck.push(fullPath);
|
|
37
36
|
} else if (entry.isDirectory() && recursive) {
|
|
38
|
-
// Simplificado: escaneo recursivo básico
|
|
39
37
|
const sub = await fs.readdir(fullPath, { withFileTypes: true });
|
|
40
38
|
for (const subEntry of sub) {
|
|
41
39
|
if (subEntry.isFile() && extPattern.test(subEntry.name)) {
|
|
@@ -70,20 +68,19 @@ export async function validate(paths, options) {
|
|
|
70
68
|
for (const res of results) {
|
|
71
69
|
const status = res.valid ? chalk.green('✅') : chalk.red('❌');
|
|
72
70
|
const filename = path.basename(res.path);
|
|
73
|
-
console.log(`${status} ${filename} (${res.valid ? 'OK' : '
|
|
71
|
+
console.log(`${status} ${filename} (${res.valid ? 'OK' : 'FALLO'})`);
|
|
74
72
|
if (!res.valid && res.missing.length > 0) {
|
|
75
73
|
console.log(chalk.gray(` Faltan: ${res.missing.join(', ')}`));
|
|
76
74
|
}
|
|
77
75
|
if (res.valid) validCount++;
|
|
78
76
|
}
|
|
79
77
|
|
|
80
|
-
console.log(chalk.gray(`\n📊 Resumen: ${validCount}/${results.length} archivos
|
|
78
|
+
console.log(chalk.gray(`\n📊 Resumen: ${validCount}/${results.length} archivos validos`));
|
|
81
79
|
}
|
|
82
80
|
|
|
83
81
|
async function validateSingleFile(filepath) {
|
|
84
82
|
const pyCode = `
|
|
85
|
-
|
|
86
|
-
sys.path.insert(0, '${path.join(__dirname, '..', 'py', 'src')}')
|
|
83
|
+
${g360CorePath(__dirname)}
|
|
87
84
|
from g360_core.scanner import ERPScanner
|
|
88
85
|
from pathlib import Path
|
|
89
86
|
|
|
@@ -100,8 +97,8 @@ except Exception as e:
|
|
|
100
97
|
`;
|
|
101
98
|
|
|
102
99
|
try {
|
|
103
|
-
const
|
|
104
|
-
const lines =
|
|
100
|
+
const { stdout } = await runPython(pyCode);
|
|
101
|
+
const lines = stdout.split('\n').filter(l => l.trim());
|
|
105
102
|
|
|
106
103
|
let valid = false;
|
|
107
104
|
let missing = [];
|
|
@@ -121,30 +118,3 @@ except Exception as e:
|
|
|
121
118
|
return { path: filepath, valid: false, missing: [err.message] };
|
|
122
119
|
}
|
|
123
120
|
}
|
|
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
|
-
}
|