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.
@@ -1,8 +1,14 @@
1
1
  import fs from 'fs-extra';
2
2
  import path from 'path';
3
3
 
4
- const IGNORE_DIRS = ['node_modules', '.git', 'dist', 'build', '.next', 'out', '.nuxt', 'coverage', '.cache', '.svelte-kit', '__pycache__', '.pytest_cache'];
4
+ const IGNORE_DIRS = ['node_modules', '.git', 'dist', 'build', '.next', 'out', '.nuxt', 'coverage', '.cache', '.svelte-kit', '__pycache__', '.pytest_cache', 'g360'];
5
5
 
6
+ /**
7
+ * Obtiene todos los archivos relativos de un directorio (recursivo)
8
+ * @param {string} dir - Directorio raiz
9
+ * @param {string} baseDir - Directorio base para paths relativos
10
+ * @returns {Promise<string[]>} Lista de paths relativos
11
+ */
6
12
  export async function getAllFiles(dir, baseDir = dir) {
7
13
  const files = [];
8
14
 
@@ -11,7 +17,7 @@ export async function getAllFiles(dir, baseDir = dir) {
11
17
  const items = fs.readdirSync(dir, { withFileTypes: true });
12
18
 
13
19
  for (const item of items) {
14
- if (IGNORE_DIRS.includes(item.name)) continue;
20
+ if (IGNORE_DIRS.includes(item.name) || item.name.startsWith('.')) continue;
15
21
 
16
22
  const fullPath = path.join(dir, item.name);
17
23
  const relativePath = path.relative(baseDir, fullPath).replace(/\\/g, '/');
@@ -25,3 +31,84 @@ export async function getAllFiles(dir, baseDir = dir) {
25
31
 
26
32
  return files;
27
33
  }
34
+
35
+ /**
36
+ * Walk recursivo de proyecto con callbacks por tipo de archivo
37
+ * Consolidacion de los walk() duplicados en lint.js, docs.js, clean.js
38
+ * @param {string} dir - Directorio a escanear
39
+ * @param {Object} callbacks - Callbacks por extension
40
+ * @param {Function} callbacks.onJs - Callback para archivos .js/.jsx
41
+ * @param {Function} callbacks.onPy - Callback para archivos .py
42
+ * @param {Function} callbacks.onFile - Callback para cualquier archivo (recibe fullPath, relativePath)
43
+ * @param {Function} callbacks.onDir - Callback para directorios (recibe relativePath)
44
+ * @param {Object} options - Opciones extras
45
+ * @param {number} options.maxDepth - Profundidad maxima (default: 10)
46
+ * @param {string[]} options.skipDirs - Directorios adicionales a ignorar
47
+ */
48
+ export function walkProject(dir, callbacks, options = {}) {
49
+ const { maxDepth = 10, skipDirs = [] } = options;
50
+ const allSkip = [...IGNORE_DIRS, ...skipDirs];
51
+
52
+ function walk(d, depth) {
53
+ if (depth > maxDepth) return;
54
+ const items = fs.readdirSync(d, { withFileTypes: true });
55
+
56
+ for (const item of items) {
57
+ if (item.name.startsWith('.') || allSkip.includes(item.name)) continue;
58
+
59
+ const fullPath = path.join(d, item.name);
60
+ const relPath = path.relative(dir, fullPath).replace(/\\/g, '/');
61
+
62
+ if (item.isDirectory()) {
63
+ if (callbacks.onDir) callbacks.onDir(relPath, fullPath);
64
+ walk(fullPath, depth + 1);
65
+ } else if (item.isFile()) {
66
+ if (callbacks.onFile) callbacks.onFile(fullPath, relPath, item.name);
67
+
68
+ if (item.name.endsWith('.js') && !item.name.endsWith('.test.js') && !item.name.endsWith('.spec.js')) {
69
+ if (callbacks.onJs) callbacks.onJs(fullPath, relPath);
70
+ }
71
+ if (item.name.endsWith('.jsx') && !item.name.endsWith('.test.jsx')) {
72
+ if (callbacks.onJs) callbacks.onJs(fullPath, relPath);
73
+ }
74
+ if (item.name.endsWith('.ts') && !item.name.endsWith('.test.ts')) {
75
+ if (callbacks.onJs) callbacks.onJs(fullPath, relPath);
76
+ }
77
+ if (item.name.endsWith('.tsx') && !item.name.endsWith('.test.tsx')) {
78
+ if (callbacks.onJs) callbacks.onJs(fullPath, relPath);
79
+ }
80
+ if (item.name.endsWith('.py')) {
81
+ if (callbacks.onPy) callbacks.onPy(fullPath, relPath);
82
+ }
83
+ }
84
+ }
85
+ }
86
+
87
+ walk(dir, 0);
88
+ }
89
+
90
+ /**
91
+ * Obtiene archivos JS/TS de un proyecto (sin tests)
92
+ * @param {string} dir - Directorio del proyecto
93
+ * @returns {string[]} Lista de paths absolutos
94
+ */
95
+ export function getJsFiles(dir) {
96
+ const files = [];
97
+ walkProject(dir, {
98
+ onJs: (fullPath) => files.push(fullPath),
99
+ });
100
+ return files;
101
+ }
102
+
103
+ /**
104
+ * Obtiene archivos Python de un proyecto (sin __init__.py)
105
+ * @param {string} dir - Directorio del proyecto
106
+ * @returns {string[]} Lista de paths absolutos
107
+ */
108
+ export function getPyFiles(dir) {
109
+ const files = [];
110
+ walkProject(dir, {
111
+ onPy: (fullPath) => files.push(fullPath),
112
+ });
113
+ return files;
114
+ }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * @file python-runner.js
3
+ * @description Runner consolidado para ejecutar codigo Python desde Node.js
4
+ * @author @carloscus
5
+ * @version 1.0.0
6
+ */
7
+
8
+ import { spawn } from 'child_process';
9
+ import path from 'path';
10
+
11
+ /**
12
+ * Ejecuta codigo Python y retorna stdout/stderr
13
+ * @param {string} code - Codigo Python a ejecutar
14
+ * @param {Object} options - Opciones opcionales
15
+ * @param {string} options.pythonPath - Ruta al interprete Python
16
+ * @param {number} options.timeout - Timeout en ms
17
+ * @returns {Promise<{stdout: string, stderr: string}>}
18
+ */
19
+ export function runPython(code, options = {}) {
20
+ const { pythonPath, timeout } = options;
21
+
22
+ return new Promise((resolve, reject) => {
23
+ const pyExec = pythonPath || process.env.PYTHON || 'python3';
24
+ const proc = spawn(pyExec, ['-c', code], {
25
+ stdio: ['ignore', 'pipe', 'pipe'],
26
+ });
27
+
28
+ let stdout = '';
29
+ let stderr = '';
30
+ let timer;
31
+
32
+ if (timeout) {
33
+ timer = setTimeout(() => {
34
+ proc.kill();
35
+ reject(new Error(`Python timeout despues de ${timeout}ms`));
36
+ }, timeout);
37
+ }
38
+
39
+ proc.stdout?.on('data', (data) => { stdout += data.toString(); });
40
+ proc.stderr?.on('data', (data) => { stderr += data.toString(); });
41
+
42
+ proc.on('close', (code) => {
43
+ if (timer) clearTimeout(timer);
44
+ if (code === 0) {
45
+ resolve({ stdout, stderr });
46
+ } else {
47
+ reject(new Error(stderr || `Python termino con codigo ${code}`));
48
+ }
49
+ });
50
+
51
+ proc.on('error', (err) => {
52
+ if (timer) clearTimeout(timer);
53
+ reject(new Error(`No se pudo ejecutar Python: ${err.message}`));
54
+ });
55
+ });
56
+ }
57
+
58
+ /**
59
+ * Ejecuta codigo Python y retorna solo stdout
60
+ * @param {string} code - Codigo Python a ejecutar
61
+ * @param {Object} options - Mismas opciones que runPython
62
+ * @returns {Promise<string>} stdout
63
+ */
64
+ export async function runPythonStdout(code, options = {}) {
65
+ const { stdout } = await runPython(code, options);
66
+ return stdout;
67
+ }
68
+
69
+ /**
70
+ * Crea el prefijo de Python para importar desde src/py/src
71
+ * @param {string} commandDir - Directorio del comando que invoca (usar __dirname o import.meta)
72
+ * @returns {string} Lineas Python para sys.path.insert
73
+ */
74
+ export function g360CorePath(commandDir) {
75
+ const pySrc = path.join(commandDir, '..', 'py', 'src').replace(/\\/g, '\\\\');
76
+ return `import sys\nsys.path.insert(0, '${pySrc}')`;
77
+ }
@@ -1,7 +0,0 @@
1
- {
2
- "name": "python-cli-template",
3
- "version": "1.0.0",
4
- "description": "G360 Python CLI Template",
5
- "main": "src/main.py",
6
- "requirements": []
7
- }