awc-zns-mtd 2.9.0 → 2.10.3

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.
Files changed (38) hide show
  1. package/.github/workflows/ci.yml +148 -0
  2. package/.husky/pre-commit +2 -0
  3. package/.prettierignore +31 -0
  4. package/.prettierrc +13 -0
  5. package/IMPLEMENTATION_SUMMARY.md +410 -0
  6. package/PHASE_2_SUMMARY.md +289 -0
  7. package/README.md +114 -47
  8. package/SECURITY.md +58 -0
  9. package/eslint.config.js +70 -0
  10. package/jest.config.js +49 -0
  11. package/package.json +40 -14
  12. package/src/modules/custom-agents/cli/awc-agent.js +505 -372
  13. package/test/integration/cli/cli-commands.integration.test.js +105 -0
  14. package/test/setup.js +22 -0
  15. package/test/unit/commands/version.test.js +39 -0
  16. package/test/unit/config/config-manager.test.js +147 -0
  17. package/test/unit/utils/file-utils.test.js +177 -0
  18. package/test/unit/utils/validators.test.js +57 -0
  19. package/tools/cli/commands/init.js +556 -513
  20. package/tools/cli/commands/new-project.js +680 -659
  21. package/tools/cli/commands/validate.js +13 -13
  22. package/tools/cli/commands/version.js +5 -3
  23. package/tools/cli/utils/console-logger.js +39 -15
  24. package/tools/cli/utils/logger.js +176 -0
  25. package/tools/cli/utils/project-analyzer.js +33 -16
  26. package/tools/cli/utils/validators.js +144 -0
  27. package/tools/cli/utils/version.js +6 -2
  28. package/tools/config/config-manager.js +243 -0
  29. package/tools/version/changelog-manager.js +301 -288
  30. package/tools/version/update-checker.js +32 -32
  31. package/tools/version/version-bump.js +89 -90
  32. package/tools/version/version-manager.js +17 -7
  33. package/tsconfig.json +47 -0
  34. package/types/index.d.ts +206 -0
  35. package/tools/cli/commands/init-old.js +0 -147
  36. package/tools/cli/commands/new-project-broken.js +0 -1302
  37. package/tools/cli/commands/new-project-old.js +0 -1302
  38. package/tools/cli/commands/new-project.js.backup +0 -1302
@@ -26,10 +26,10 @@ async function validateCommand() {
26
26
  checksCount++;
27
27
  if (await fs.pathExists(awcDir)) {
28
28
  passedCount++;
29
- console.log(chalk.green('✓') + ' Directorio .awc existe');
29
+ console.log(`${chalk.green('✓')} Directorio .awc existe`);
30
30
  } else {
31
31
  errors.push('Directorio .awc no encontrado');
32
- console.log(chalk.red('✗') + ' Directorio .awc no encontrado');
32
+ console.log(`${chalk.red('✗')} Directorio .awc no encontrado`);
33
33
  }
34
34
 
35
35
  // Check 2: Configuración
@@ -37,10 +37,10 @@ async function validateCommand() {
37
37
  const config = await loadConfig(awcDir);
38
38
  if (config) {
39
39
  passedCount++;
40
- console.log(chalk.green('✓') + ' Archivo de configuración válido');
40
+ console.log(`${chalk.green('✓')} Archivo de configuración válido`);
41
41
  } else {
42
42
  errors.push('Archivo de configuración inválido o faltante');
43
- console.log(chalk.red('✗') + ' Archivo de configuración inválido');
43
+ console.log(`${chalk.red('✗')} Archivo de configuración inválido`);
44
44
  }
45
45
 
46
46
  // Check 3: Agentes
@@ -48,18 +48,18 @@ async function validateCommand() {
48
48
  const agentsPath = path.join(awcDir, 'agents');
49
49
  if (await fs.pathExists(agentsPath)) {
50
50
  const agentFiles = await fs.readdir(agentsPath);
51
- const yamlAgents = agentFiles.filter(f => f.endsWith('.yaml'));
51
+ const yamlAgents = agentFiles.filter((f) => f.endsWith('.yaml'));
52
52
 
53
53
  if (yamlAgents.length >= 4) {
54
54
  passedCount++;
55
- console.log(chalk.green('✓') + ` ${yamlAgents.length} agentes encontrados`);
55
+ console.log(`${chalk.green('✓')} ${yamlAgents.length} agentes encontrados`);
56
56
  } else {
57
57
  warnings.push(`Solo ${yamlAgents.length} agentes encontrados (se esperan 4)`);
58
- console.log(chalk.yellow('⚠') + ` Solo ${yamlAgents.length} agentes (se esperan 4)`);
58
+ console.log(`${chalk.yellow('⚠')} Solo ${yamlAgents.length} agentes (se esperan 4)`);
59
59
  }
60
60
  } else {
61
61
  errors.push('Directorio de agentes no encontrado');
62
- console.log(chalk.red('✗') + ' Directorio de agentes no encontrado');
62
+ console.log(`${chalk.red('✗')} Directorio de agentes no encontrado`);
63
63
  }
64
64
 
65
65
  // Check 4: Workflows
@@ -70,14 +70,14 @@ async function validateCommand() {
70
70
 
71
71
  if (workflowDirs.length >= 3) {
72
72
  passedCount++;
73
- console.log(chalk.green('✓') + ` ${workflowDirs.length} workflows encontrados`);
73
+ console.log(`${chalk.green('✓')} ${workflowDirs.length} workflows encontrados`);
74
74
  } else {
75
75
  warnings.push(`Solo ${workflowDirs.length} workflows encontrados (se esperan 3)`);
76
- console.log(chalk.yellow('⚠') + ` Solo ${workflowDirs.length} workflows (se esperan 3)`);
76
+ console.log(`${chalk.yellow('⚠')} Solo ${workflowDirs.length} workflows (se esperan 3)`);
77
77
  }
78
78
  } else {
79
79
  errors.push('Directorio de workflows no encontrado');
80
- console.log(chalk.red('✗') + ' Directorio de workflows no encontrado');
80
+ console.log(`${chalk.red('✗')} Directorio de workflows no encontrado`);
81
81
  }
82
82
 
83
83
  // Check 5: Documentación
@@ -85,7 +85,7 @@ async function validateCommand() {
85
85
  const docsPath = path.join(cwd, 'docs');
86
86
  if (await fs.pathExists(docsPath)) {
87
87
  passedCount++;
88
- console.log(chalk.green('✓') + ' Directorio de documentación existe');
88
+ console.log(`${chalk.green('✓')} Directorio de documentación existe`);
89
89
 
90
90
  // Sub-checks de documentación
91
91
  const adrPath = path.join(docsPath, 'adr');
@@ -103,7 +103,7 @@ async function validateCommand() {
103
103
  }
104
104
  } else {
105
105
  warnings.push('Directorio de documentación no encontrado');
106
- console.log(chalk.yellow('⚠') + ' Directorio de documentación no encontrado');
106
+ console.log(`${chalk.yellow('⚠')} Directorio de documentación no encontrado`);
107
107
  }
108
108
 
109
109
  // Resumen
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  const chalk = require('chalk');
7
- const semver = require('semver');
7
+ // const semver = require('semver');
8
8
  const { getVersion, checkForUpdates } = require('../utils/version');
9
9
  const { displayLogo } = require('../utils/console-logger');
10
10
 
@@ -35,7 +35,7 @@ async function versionCommand() {
35
35
  } else {
36
36
  console.log(chalk.green('\n✅ Estás usando la versión más reciente\n'));
37
37
  }
38
- } catch (error) {
38
+ } catch {
39
39
  console.log(chalk.gray('\n No se pudo verificar actualizaciones (sin conexión)\n'));
40
40
  }
41
41
 
@@ -43,7 +43,9 @@ async function versionCommand() {
43
43
  console.log(chalk.cyan('📚 Más información:\n'));
44
44
  console.log(` Documentación: ${chalk.blue('https://github.com/awc/awc-zns-mtd')}`);
45
45
  console.log(` Issues: ${chalk.blue('https://github.com/awc/awc-zns-mtd/issues')}`);
46
- console.log(` Changelog: ${chalk.blue('https://github.com/awc/awc-zns-mtd/blob/main/CHANGELOG.md')}\n`);
46
+ console.log(
47
+ ` Changelog: ${chalk.blue('https://github.com/awc/awc-zns-mtd/blob/main/CHANGELOG.md')}\n`
48
+ );
47
49
  }
48
50
 
49
51
  module.exports = { versionCommand };
@@ -11,8 +11,16 @@ const chalk = require('chalk');
11
11
  function displayLogo() {
12
12
  console.log('');
13
13
  console.log(chalk.cyan('╔═══════════════════════════════════════════════════════════╗'));
14
- console.log(chalk.cyan('║') + chalk.bold.white(' ZΞNAPSΞS by ΛNWICO ') + chalk.cyan('║'));
15
- console.log(chalk.cyan('║') + chalk.gray(' Minimalismo Estratégico Method ') + chalk.cyan('║'));
14
+ console.log(
15
+ chalk.cyan('║') +
16
+ chalk.bold.white(' ZΞNAPSΞS by ΛNWICO ') +
17
+ chalk.cyan('║')
18
+ );
19
+ console.log(
20
+ chalk.cyan('║') +
21
+ chalk.gray(' Minimalismo Estratégico Method ') +
22
+ chalk.cyan('║')
23
+ );
16
24
  console.log(chalk.cyan('╚═══════════════════════════════════════════════════════════╝'));
17
25
  console.log();
18
26
  }
@@ -21,28 +29,28 @@ function displayLogo() {
21
29
  * Muestra un mensaje de éxito
22
30
  */
23
31
  function logSuccess(message) {
24
- console.log(chalk.green('✓') + ' ' + message);
32
+ console.log(`${chalk.green('✓')} ${message}`);
25
33
  }
26
34
 
27
35
  /**
28
36
  * Muestra un mensaje de error
29
37
  */
30
38
  function logError(message) {
31
- console.log(chalk.red('✗') + ' ' + message);
39
+ console.log(`${chalk.red('✗')} ${message}`);
32
40
  }
33
41
 
34
42
  /**
35
43
  * Muestra un mensaje de advertencia
36
44
  */
37
45
  function logWarning(message) {
38
- console.log(chalk.yellow('⚠') + ' ' + message);
46
+ console.log(`${chalk.yellow('⚠')} ${message}`);
39
47
  }
40
48
 
41
49
  /**
42
50
  * Muestra un mensaje informativo
43
51
  */
44
52
  function logInfo(message) {
45
- console.log(chalk.blue('ℹ') + ' ' + message);
53
+ console.log(`${chalk.blue('ℹ')} ${message}`);
46
54
  }
47
55
 
48
56
  /**
@@ -74,9 +82,9 @@ function logProgress(current, total, message) {
74
82
  * Muestra una tabla simple
75
83
  */
76
84
  function logTable(data) {
77
- const maxKeyLength = Math.max(...data.map(item => item.key.length));
85
+ const maxKeyLength = Math.max(...data.map((item) => item.key.length));
78
86
 
79
- data.forEach(item => {
87
+ data.forEach((item) => {
80
88
  const paddedKey = item.key.padEnd(maxKeyLength + 2);
81
89
  console.log(` ${chalk.gray(paddedKey)} ${chalk.yellow(item.value)}`);
82
90
  });
@@ -88,7 +96,9 @@ function logTable(data) {
88
96
  function displayWelcome(projectName) {
89
97
  console.log();
90
98
  console.log(chalk.cyan('╔═══════════════════════════════════════════════════════════╗'));
91
- console.log(chalk.cyan('║') + chalk.bold.white(` Bienvenido a ${projectName}`.padEnd(58)) + chalk.cyan('║'));
99
+ console.log(
100
+ chalk.cyan('║') + chalk.bold.white(` Bienvenido a ${projectName}`.padEnd(58)) + chalk.cyan('║')
101
+ );
92
102
  console.log(chalk.cyan('╚═══════════════════════════════════════════════════════════╝'));
93
103
  console.log();
94
104
  }
@@ -99,7 +109,7 @@ function displayWelcome(projectName) {
99
109
  function displayCommands(commands) {
100
110
  console.log(chalk.cyan('\n💡 Comandos disponibles:\n'));
101
111
 
102
- commands.forEach(cmd => {
112
+ commands.forEach((cmd) => {
103
113
  const command = chalk.green(cmd.command.padEnd(20));
104
114
  const description = chalk.gray(cmd.description);
105
115
  console.log(` ${command} ${description}`);
@@ -113,9 +123,15 @@ function displayCommands(commands) {
113
123
  */
114
124
  function displayZnsPhilosophy() {
115
125
  console.log(chalk.cyan('\n📖 Filosofía ZNS-MTD:\n'));
116
- console.log(chalk.yellow(' ZEN') + chalk.gray(' → Claridad, simplicidad deliberada, ruido mínimo'));
117
- console.log(chalk.yellow(' NEUTRO') + chalk.gray(' Objetividad, decisiones basadas en evidencia'));
118
- console.log(chalk.yellow(' SISTEMÁTICO') + chalk.gray(' → Procesos repetibles, documentación rigurosa'));
126
+ console.log(
127
+ chalk.yellow(' ZEN') + chalk.gray(' Claridad, simplicidad deliberada, ruido mínimo')
128
+ );
129
+ console.log(
130
+ chalk.yellow(' NEUTRO') + chalk.gray(' → Objetividad, decisiones basadas en evidencia')
131
+ );
132
+ console.log(
133
+ chalk.yellow(' SISTEMÁTICO') + chalk.gray(' → Procesos repetibles, documentación rigurosa')
134
+ );
119
135
  console.log();
120
136
  }
121
137
 
@@ -126,11 +142,19 @@ function displayCompletionBanner(success = true) {
126
142
  console.log();
127
143
  if (success) {
128
144
  console.log(chalk.green('╔═══════════════════════════════════════════════════════════╗'));
129
- console.log(chalk.green('║') + chalk.bold.white(' ✅ PROCESO COMPLETADO ') + chalk.green('║'));
145
+ console.log(
146
+ chalk.green('║') +
147
+ chalk.bold.white(' ✅ PROCESO COMPLETADO ') +
148
+ chalk.green('║')
149
+ );
130
150
  console.log(chalk.green('╚═══════════════════════════════════════════════════════════╝'));
131
151
  } else {
132
152
  console.log(chalk.red('╔═══════════════════════════════════════════════════════════╗'));
133
- console.log(chalk.red('║') + chalk.bold.white(' ❌ PROCESO FALLIDO ') + chalk.red('║'));
153
+ console.log(
154
+ chalk.red('║') +
155
+ chalk.bold.white(' ❌ PROCESO FALLIDO ') +
156
+ chalk.red('║')
157
+ );
134
158
  console.log(chalk.red('╚═══════════════════════════════════════════════════════════╝'));
135
159
  }
136
160
  console.log();
@@ -0,0 +1,176 @@
1
+ /**
2
+ * Logger - Sistema de logging estructurado
3
+ * Utiliza Winston para logging centralizado
4
+ */
5
+
6
+ const winston = require('winston');
7
+ const path = require('path');
8
+ const fs = require('fs-extra');
9
+ const ConfigManager = require('../../config/config-manager');
10
+
11
+ // Asegurar que existe el directorio de logs
12
+ const logsDir = path.join(process.cwd(), ConfigManager.LOGS_DIR);
13
+ fs.ensureDirSync(logsDir);
14
+
15
+ /**
16
+ * Formatos personalizados
17
+ */
18
+ const customFormat = winston.format.combine(
19
+ winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
20
+ winston.format.errors({ stack: true }),
21
+ winston.format.splat(),
22
+ winston.format.json()
23
+ );
24
+
25
+ const consoleFormat = winston.format.combine(
26
+ winston.format.colorize(),
27
+ winston.format.timestamp({ format: 'HH:mm:ss' }),
28
+ winston.format.printf(({ level, message, timestamp, ...metadata }) => {
29
+ let msg = `${timestamp} [${level}]: ${message}`;
30
+ if (Object.keys(metadata).length > 0) {
31
+ msg += ` ${JSON.stringify(metadata)}`;
32
+ }
33
+ return msg;
34
+ })
35
+ );
36
+
37
+ /**
38
+ * Crear logger
39
+ */
40
+ const logger = winston.createLogger({
41
+ level: ConfigManager.DEFAULT_LOG_LEVEL,
42
+ format: customFormat,
43
+ defaultMeta: { service: 'awc-zns-mtd' },
44
+ transports: [
45
+ // Logs de error
46
+ new winston.transports.File({
47
+ filename: path.join(logsDir, 'error.log'),
48
+ level: 'error',
49
+ maxsize: 5242880, // 5MB
50
+ maxFiles: 5
51
+ }),
52
+
53
+ // Logs combinados
54
+ new winston.transports.File({
55
+ filename: path.join(logsDir, 'combined.log'),
56
+ maxsize: 5242880,
57
+ maxFiles: 5
58
+ }),
59
+
60
+ // Logs de comandos (solo info y superior)
61
+ new winston.transports.File({
62
+ filename: path.join(logsDir, 'commands.log'),
63
+ level: 'info',
64
+ maxsize: 5242880,
65
+ maxFiles: 3
66
+ })
67
+ ],
68
+
69
+ // Manejo de excepciones
70
+ exceptionHandlers: [
71
+ new winston.transports.File({
72
+ filename: path.join(logsDir, 'exceptions.log')
73
+ })
74
+ ],
75
+
76
+ // Manejo de rechazos de promesas
77
+ rejectionHandlers: [
78
+ new winston.transports.File({
79
+ filename: path.join(logsDir, 'rejections.log')
80
+ })
81
+ ]
82
+ });
83
+
84
+ // En desarrollo, también loguear a consola
85
+ if (process.env.NODE_ENV !== 'production') {
86
+ logger.add(
87
+ new winston.transports.Console({
88
+ format: consoleFormat,
89
+ level: 'debug'
90
+ })
91
+ );
92
+ }
93
+
94
+ /**
95
+ * Wrapper para comandos CLI
96
+ */
97
+ class CLILogger {
98
+ /**
99
+ * Log de inicio de comando
100
+ * @param {string} command - Nombre del comando
101
+ * @param {Object} options - Opciones del comando
102
+ */
103
+ static commandStart(command, options = {}) {
104
+ logger.info('Command started', {
105
+ command,
106
+ options,
107
+ timestamp: new Date().toISOString()
108
+ });
109
+ }
110
+
111
+ /**
112
+ * Log de finalización de comando
113
+ * @param {string} command - Nombre del comando
114
+ * @param {boolean} success - Si fue exitoso
115
+ * @param {number} duration - Duración en ms
116
+ */
117
+ static commandEnd(command, success = true, duration = 0) {
118
+ const level = success ? 'info' : 'error';
119
+ logger[level]('Command finished', {
120
+ command,
121
+ success,
122
+ duration: `${duration}ms`,
123
+ timestamp: new Date().toISOString()
124
+ });
125
+ }
126
+
127
+ /**
128
+ * Log de error de comando
129
+ * @param {string} command - Nombre del comando
130
+ * @param {Error} error - Error ocurrido
131
+ */
132
+ static commandError(command, error) {
133
+ logger.error('Command error', {
134
+ command,
135
+ error: error.message,
136
+ stack: error.stack,
137
+ timestamp: new Date().toISOString()
138
+ });
139
+ }
140
+
141
+ /**
142
+ * Log de operación de archivo
143
+ * @param {string} operation - Tipo de operación
144
+ * @param {string} filePath - Path del archivo
145
+ * @param {boolean} success - Si fue exitoso
146
+ */
147
+ static fileOperation(operation, filePath, success = true) {
148
+ logger.info('File operation', {
149
+ operation,
150
+ filePath,
151
+ success,
152
+ timestamp: new Date().toISOString()
153
+ });
154
+ }
155
+
156
+ /**
157
+ * Log de validación
158
+ * @param {string} type - Tipo de validación
159
+ * @param {boolean} valid - Si es válido
160
+ * @param {Array<string>} errors - Errores encontrados
161
+ */
162
+ static validation(type, valid, errors = []) {
163
+ const level = valid ? 'info' : 'warn';
164
+ logger[level]('Validation', {
165
+ type,
166
+ valid,
167
+ errors,
168
+ timestamp: new Date().toISOString()
169
+ });
170
+ }
171
+ }
172
+
173
+ module.exports = {
174
+ logger,
175
+ CLILogger
176
+ };
@@ -50,7 +50,6 @@ async function analyzeProject(projectPath) {
50
50
 
51
51
  // Calcular tamaño
52
52
  analysis.size = calculateSize(analysis);
53
-
54
53
  } catch (error) {
55
54
  console.error('Error en análisis:', error.message);
56
55
  }
@@ -69,16 +68,16 @@ async function detectTechnologies(projectPath) {
69
68
  'yarn.lock': ['Yarn'],
70
69
  'pnpm-lock.yaml': ['pnpm'],
71
70
  'requirements.txt': ['Python', 'pip'],
72
- 'Pipfile': ['Python', 'Pipenv'],
71
+ Pipfile: ['Python', 'Pipenv'],
73
72
  'pyproject.toml': ['Python', 'Poetry'],
74
- 'Gemfile': ['Ruby', 'Bundler'],
73
+ Gemfile: ['Ruby', 'Bundler'],
75
74
  'Cargo.toml': ['Rust', 'Cargo'],
76
75
  'go.mod': ['Go'],
77
76
  'pom.xml': ['Java', 'Maven'],
78
77
  'build.gradle': ['Java/Kotlin', 'Gradle'],
79
78
  'composer.json': ['PHP', 'Composer'],
80
79
  '.csproj': ['C#', '.NET'],
81
- 'Dockerfile': ['Docker'],
80
+ Dockerfile: ['Docker'],
82
81
  'docker-compose.yml': ['Docker Compose'],
83
82
  '.gitignore': ['Git'],
84
83
  'tsconfig.json': ['TypeScript'],
@@ -198,7 +197,7 @@ async function detectBuildTools(projectPath) {
198
197
  'rollup.config.js': 'Rollup',
199
198
  'gulpfile.js': 'Gulp',
200
199
  'Gruntfile.js': 'Grunt',
201
- 'Makefile': 'Make',
200
+ Makefile: 'Make',
202
201
  'CMakeLists.txt': 'CMake'
203
202
  };
204
203
 
@@ -220,21 +219,35 @@ function calculateComplexity(analysis) {
220
219
  let score = 0;
221
220
 
222
221
  // Basado en cantidad de archivos
223
- if (fileCount > 1000) score += 3;
224
- else if (fileCount > 500) score += 2;
225
- else if (fileCount > 100) score += 1;
222
+ if (fileCount > 1000) {
223
+ score += 3;
224
+ } else if (fileCount > 500) {
225
+ score += 2;
226
+ } else if (fileCount > 100) {
227
+ score += 1;
228
+ }
226
229
 
227
230
  // Basado en cantidad de directorios
228
- if (directoryCount > 100) score += 2;
229
- else if (directoryCount > 50) score += 1;
231
+ if (directoryCount > 100) {
232
+ score += 2;
233
+ } else if (directoryCount > 50) {
234
+ score += 1;
235
+ }
230
236
 
231
237
  // Basado en cantidad de tecnologías
232
- if (technologies.length > 10) score += 2;
233
- else if (technologies.length > 5) score += 1;
238
+ if (technologies.length > 10) {
239
+ score += 2;
240
+ } else if (technologies.length > 5) {
241
+ score += 1;
242
+ }
234
243
 
235
244
  // Determinar nivel de complejidad
236
- if (score >= 5) return 'high';
237
- if (score >= 3) return 'medium';
245
+ if (score >= 5) {
246
+ return 'high';
247
+ }
248
+ if (score >= 3) {
249
+ return 'medium';
250
+ }
238
251
  return 'low';
239
252
  }
240
253
 
@@ -244,8 +257,12 @@ function calculateComplexity(analysis) {
244
257
  function calculateSize(analysis) {
245
258
  const { fileCount } = analysis;
246
259
 
247
- if (fileCount > 1000) return 'large';
248
- if (fileCount > 100) return 'medium';
260
+ if (fileCount > 1000) {
261
+ return 'large';
262
+ }
263
+ if (fileCount > 100) {
264
+ return 'medium';
265
+ }
249
266
  return 'small';
250
267
  }
251
268
 
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Utilidades de validación
3
+ * Validadores para inputs del usuario y paths del filesystem
4
+ */
5
+
6
+ const path = require('path');
7
+
8
+ /**
9
+ * Valida un nombre de proyecto
10
+ * @param {string} name - Nombre del proyecto
11
+ * @returns {boolean} - true si es válido
12
+ */
13
+ function validateProjectName(name) {
14
+ if (!name || typeof name !== 'string') {
15
+ return false;
16
+ }
17
+
18
+ const trimmed = name.trim();
19
+
20
+ // Verificar que no esté vacío
21
+ if (trimmed.length === 0) {
22
+ return false;
23
+ }
24
+
25
+ // Solo letras, números, guiones y guiones bajos
26
+ const validPattern = /^[a-zA-Z0-9-_]+$/;
27
+ if (!validPattern.test(trimmed)) {
28
+ return false;
29
+ }
30
+
31
+ // Nombres reservados
32
+ const reserved = [
33
+ 'node_modules',
34
+ 'package.json',
35
+ 'package-lock.json',
36
+ '.git',
37
+ '.awc',
38
+ 'test',
39
+ 'dist',
40
+ 'build'
41
+ ];
42
+
43
+ if (reserved.includes(trimmed.toLowerCase())) {
44
+ return false;
45
+ }
46
+
47
+ // No debe contener path traversal
48
+ if (trimmed.includes('..') || trimmed.includes('/') || trimmed.includes('\\')) {
49
+ return false;
50
+ }
51
+
52
+ return true;
53
+ }
54
+
55
+ /**
56
+ * Valida que un path sea seguro
57
+ * @param {string} filePath - Path a validar
58
+ * @returns {boolean} - true si es seguro
59
+ */
60
+ function validatePath(filePath) {
61
+ if (!filePath || typeof filePath !== 'string') {
62
+ return false;
63
+ }
64
+
65
+ // Normalizar el path
66
+ const normalized = path.normalize(filePath);
67
+
68
+ // Detectar path traversal
69
+ if (normalized.includes('..')) {
70
+ return false;
71
+ }
72
+
73
+ // Paths del sistema prohibidos (lista básica)
74
+ const forbiddenPaths = [
75
+ '/etc',
76
+ '/sys',
77
+ '/proc',
78
+ 'C:\\Windows',
79
+ 'C:\\Program Files',
80
+ '/usr/bin',
81
+ '/usr/sbin'
82
+ ];
83
+
84
+ for (const forbidden of forbiddenPaths) {
85
+ if (normalized.startsWith(forbidden)) {
86
+ return false;
87
+ }
88
+ }
89
+
90
+ return true;
91
+ }
92
+
93
+ /**
94
+ * Sanitiza un path removiendo caracteres peligrosos
95
+ * @param {string} filePath - Path a sanitizar
96
+ * @returns {string} - Path sanitizado
97
+ */
98
+ function sanitizePath(filePath) {
99
+ if (!filePath) {
100
+ return '';
101
+ }
102
+
103
+ let sanitized = filePath;
104
+
105
+ // Normalizar separadores
106
+ sanitized = sanitized.replace(/\\/g, '/');
107
+
108
+ // Eliminar múltiples slashes
109
+ sanitized = sanitized.replace(/\/+/g, '/');
110
+
111
+ // Eliminar path traversal
112
+ sanitized = sanitized.replace(/\.\.\//g, '');
113
+ sanitized = sanitized.replace(/\.\//g, '');
114
+
115
+ return path.normalize(sanitized);
116
+ }
117
+
118
+ /**
119
+ * Valida opciones del comando
120
+ * @param {Object} options - Opciones a validar
121
+ * @param {Array<string>} required - Campos requeridos
122
+ * @returns {Object} - { valid: boolean, errors: string[] }
123
+ */
124
+ function validateCommandOptions(options, required = []) {
125
+ const errors = [];
126
+
127
+ for (const field of required) {
128
+ if (!options[field]) {
129
+ errors.push(`Campo requerido: ${field}`);
130
+ }
131
+ }
132
+
133
+ return {
134
+ valid: errors.length === 0,
135
+ errors
136
+ };
137
+ }
138
+
139
+ module.exports = {
140
+ validateProjectName,
141
+ validatePath,
142
+ sanitizePath,
143
+ validateCommandOptions
144
+ };
@@ -47,8 +47,12 @@ async function checkForUpdates(currentVersion) {
47
47
  * @returns {number} -1 si v1 < v2, 0 si v1 === v2, 1 si v1 > v2
48
48
  */
49
49
  function compareVersions(v1, v2) {
50
- if (semver.lt(v1, v2)) return -1;
51
- if (semver.gt(v1, v2)) return 1;
50
+ if (semver.lt(v1, v2)) {
51
+ return -1;
52
+ }
53
+ if (semver.gt(v1, v2)) {
54
+ return 1;
55
+ }
52
56
  return 0;
53
57
  }
54
58