claude-git-hooks 1.1.0 → 1.2.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/CHANGELOG.md +97 -0
- package/README.md +12 -30
- package/bin/claude-hooks +451 -53
- package/package.json +2 -1
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
Todos los cambios notables en este proyecto se documentarán en este archivo.
|
|
4
|
+
|
|
5
|
+
El formato está basado en [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
y este proyecto adhiere a [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [1.2.1] - 2024-07-24
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- 🐛 Corregido problema de bloqueo del spinner durante verificación de autenticación Claude
|
|
12
|
+
- 🎨 Arreglado sistema de entretenimiento para mostrar correctamente spinner, chistes y countdown
|
|
13
|
+
- 🔧 Solucionado problema de líneas duplicadas en consola después del segundo chiste
|
|
14
|
+
- 🧹 Mejorada limpieza de pantalla para evitar superposición de texto al finalizar spinner
|
|
15
|
+
- ⏱️ Aumentado timeout de verificación de autenticación a 2 minutos
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
- 🔄 Reemplazado `execSync` por `spawn` en verificación de autenticación para evitar bloqueos
|
|
19
|
+
- 📍 Mejorado sistema de posicionamiento del cursor para renderizado consistente
|
|
20
|
+
- 🎯 Optimizada lógica de actualización del spinner para evitar race conditions
|
|
21
|
+
|
|
22
|
+
### Technical
|
|
23
|
+
- 🏗️ Uso de `spawn` con `stdio: 'ignore'` para mantener compatibilidad con comportamiento original
|
|
24
|
+
- 🎮 Sistema de reserva de espacio (3 líneas) para evitar problemas de renderizado
|
|
25
|
+
- 🧮 Contador de chistes cada 10 segundos real (sin drift de tiempo)
|
|
26
|
+
- 🔍 Limpieza precisa línea por línea al finalizar para evitar artefactos visuales
|
|
27
|
+
|
|
28
|
+
## [1.2.0] - 2024-07-24
|
|
29
|
+
|
|
30
|
+
### Added
|
|
31
|
+
- ✨ Sistema de entretenimiento con spinner animado y chistes durante operaciones largas
|
|
32
|
+
- 🔐 Lectura segura y no-persistente de contraseña sudo para instalación automática
|
|
33
|
+
- 🛠️ Verificación completa de dependencias del sistema (jq, curl, herramientas Unix)
|
|
34
|
+
- 🚀 Instalación automática de dependencias faltantes con contraseña sudo
|
|
35
|
+
- ⚙️ Configuración automática de Git (line endings para WSL/Windows)
|
|
36
|
+
- 🎭 Chistes rotativos cada 10 segundos durante verificación de autenticación Claude
|
|
37
|
+
|
|
38
|
+
### Enhanced
|
|
39
|
+
- 📦 Función `install` ahora incluye verificación e instalación completa de dependencias
|
|
40
|
+
- 🔍 Verificación de autenticación Claude con entretenimiento visual
|
|
41
|
+
- 💻 Detección inteligente de plataforma (Linux/macOS) para instalación de paquetes
|
|
42
|
+
|
|
43
|
+
### Removed
|
|
44
|
+
- 🗑️ Eliminados archivos `setup-wsl.sh` y `setup-wsl.js` (redundantes)
|
|
45
|
+
- 🧹 Simplificación de la arquitectura eliminando duplicación de código
|
|
46
|
+
|
|
47
|
+
### Technical
|
|
48
|
+
- 🔧 Añadido módulo `https` para obtener chistes de API externa
|
|
49
|
+
- 🎨 Clase `Entertainment` para manejo de spinner y chistes
|
|
50
|
+
- 🔐 Funciones `readPassword()` y `testSudoPassword()` para manejo seguro de credenciales
|
|
51
|
+
- ⚡ Conversión a funciones async/await para mejor manejo de procesos asíncronos
|
|
52
|
+
|
|
53
|
+
## [1.1.0] - 2024-07-24
|
|
54
|
+
|
|
55
|
+
### Added
|
|
56
|
+
- 🎯 Comando `set-mode` para cambiar entre análisis estándar y SonarQube
|
|
57
|
+
- 📊 Modo SonarQube con métricas detalladas y quality gate
|
|
58
|
+
- 📈 Visualización del modo de análisis actual en comando `status`
|
|
59
|
+
- 🔧 Configuración persistente del modo de análisis en archivo `.claude-analysis-mode`
|
|
60
|
+
|
|
61
|
+
### Enhanced
|
|
62
|
+
- 📋 Comando `status` ahora muestra modo de análisis y archivos de pautas
|
|
63
|
+
- 📚 Documentación mejorada con ejemplos de ambos modos
|
|
64
|
+
- 🎨 Mejor formateo de salida en comandos informativos
|
|
65
|
+
|
|
66
|
+
### Technical
|
|
67
|
+
- 📁 Detección automática de archivos de pautas (CLAUDE_PRE_COMMIT.md, CLAUDE_PRE_COMMIT_SONAR.md)
|
|
68
|
+
- 🏗️ Estructura preparada para múltiples modos de análisis
|
|
69
|
+
|
|
70
|
+
## [1.0.0] - 2024-07-24
|
|
71
|
+
|
|
72
|
+
### Added
|
|
73
|
+
- 🎉 Primera versión estable del paquete npm `claude-git-hooks`
|
|
74
|
+
- 📦 CLI global `claude-hooks` para gestión de hooks de Git
|
|
75
|
+
- 🪝 Instalación automática de hooks `pre-commit` y `prepare-commit-msg`
|
|
76
|
+
- 🔍 Verificación de dependencias (Claude CLI, jq)
|
|
77
|
+
- 📋 Comandos principales: `install`, `uninstall`, `enable`, `disable`, `status`
|
|
78
|
+
- 📖 Sistema de ayuda completo con ejemplos
|
|
79
|
+
- 🎛️ Gestión individual de hooks (habilitar/deshabilitar por separado)
|
|
80
|
+
- 📂 Creación automática de archivos de pautas si no existen
|
|
81
|
+
- 🔄 Sistema de backup automático de hooks existentes
|
|
82
|
+
|
|
83
|
+
### Technical
|
|
84
|
+
- 🏗️ Arquitectura modular con funciones especializadas
|
|
85
|
+
- 🎨 Sistema de colores para output legible
|
|
86
|
+
- 🛡️ Validaciones de entorno (repositorio Git, dependencias)
|
|
87
|
+
- 📝 Documentación completa en README
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## Tipos de cambios
|
|
92
|
+
- `Added` - para nuevas funcionalidades
|
|
93
|
+
- `Changed` - para cambios en funcionalidades existentes
|
|
94
|
+
- `Deprecated` - para funcionalidades que serán removidas
|
|
95
|
+
- `Removed` - para funcionalidades removidas
|
|
96
|
+
- `Fixed` - para corrección de bugs
|
|
97
|
+
- `Security` - para vulnerabilidades corregidas
|
package/README.md
CHANGED
|
@@ -7,7 +7,6 @@ Este directorio contiene un pre-commit hook que utiliza Claude CLI para revisar
|
|
|
7
7
|
- **`README.md`** - Esta documentación
|
|
8
8
|
- **`pre-commit`** - Hook principal para análisis de código (con auto-actualización)
|
|
9
9
|
- **`prepare-commit-msg`** - Hook para generar mensajes de commit automáticos
|
|
10
|
-
- **`setup-wsl.sh`** - Script de instalación inicial para WSL
|
|
11
10
|
- **`.gitattributes`** - Configuración para mantener line endings correctos
|
|
12
11
|
- **`CLAUDE_PRE_COMMIT.md`** - Pautas de evaluación formato estándar
|
|
13
12
|
- **`CLAUDE_PRE_COMMIT_SONAR.md`** - Pautas de evaluación formato SonarQube
|
|
@@ -51,28 +50,31 @@ git config --global credential.helper store
|
|
|
51
50
|
|
|
52
51
|
## 🚀 Instalación
|
|
53
52
|
|
|
54
|
-
###
|
|
53
|
+
### Paquete NPM Global (Recomendado)
|
|
55
54
|
|
|
56
55
|
**IMPORTANTE**: Claude CLI corre en WSL, por lo que toda la instalación y uso de git debe hacerse desde la terminal WSL.
|
|
57
56
|
|
|
58
|
-
#### Instalación Global
|
|
59
|
-
|
|
60
57
|
```bash
|
|
61
58
|
# Instalar el paquete globalmente
|
|
62
|
-
|
|
59
|
+
npm install -g claude-git-hooks
|
|
63
60
|
|
|
64
61
|
# En cualquier repositorio, instalar los hooks
|
|
65
62
|
cd tu-proyecto
|
|
66
63
|
claude-hooks install
|
|
67
64
|
```
|
|
68
65
|
|
|
66
|
+
El comando `claude-hooks install` ahora incluye:
|
|
67
|
+
- ✅ Verificación completa de dependencias del sistema
|
|
68
|
+
- ✅ Instalación automática de paquetes faltantes (jq, curl)
|
|
69
|
+
- ✅ Configuración de Git (line endings WSL/Windows)
|
|
70
|
+
- ✅ Verificación de autenticación Claude con entretenimiento
|
|
71
|
+
- ✅ Instalación de hooks y archivos de pautas
|
|
72
|
+
|
|
69
73
|
#### Añadir como Dependencia de Desarrollo
|
|
70
74
|
|
|
71
75
|
```bash
|
|
72
76
|
# Instalar como devDependency
|
|
73
77
|
npm install --save-dev claude-git-hooks
|
|
74
|
-
|
|
75
|
-
# Añadir script al package.json
|
|
76
78
|
```
|
|
77
79
|
|
|
78
80
|
Luego añade esto a tu `package.json`:
|
|
@@ -83,30 +85,11 @@ Luego añade esto a tu `package.json`:
|
|
|
83
85
|
"postinstall": "claude-hooks install"
|
|
84
86
|
},
|
|
85
87
|
"devDependencies": {
|
|
86
|
-
"claude-git-hooks": "^1.
|
|
88
|
+
"claude-git-hooks": "^1.2.0"
|
|
87
89
|
}
|
|
88
90
|
}
|
|
89
91
|
```
|
|
90
92
|
|
|
91
|
-
### Opción 2: Instalación Local con Scripts
|
|
92
|
-
|
|
93
|
-
```bash
|
|
94
|
-
# Desde WSL - Instalación completa
|
|
95
|
-
./git-hooks/setup-wsl.sh
|
|
96
|
-
|
|
97
|
-
# Ver opciones disponibles
|
|
98
|
-
./git-hooks/setup-wsl.sh --help
|
|
99
|
-
|
|
100
|
-
# Solo sincronizar hooks (útil después de modificar archivos fuente)
|
|
101
|
-
./git-hooks/setup-wsl.sh --sync
|
|
102
|
-
|
|
103
|
-
# Habilitar/deshabilitar hooks
|
|
104
|
-
./git-hooks/setup-wsl.sh --enable-hooks # Habilita todos los hooks
|
|
105
|
-
./git-hooks/setup-wsl.sh --disable-hooks # Deshabilita todos los hooks
|
|
106
|
-
./git-hooks/setup-wsl.sh --enable-hooks pre-commit # Habilita solo pre-commit
|
|
107
|
-
./git-hooks/setup-wsl.sh --disable-hooks prepare-commit-msg # Deshabilita solo prepare-commit-msg
|
|
108
|
-
```
|
|
109
|
-
|
|
110
93
|
## 🤖 Características
|
|
111
94
|
|
|
112
95
|
**✨ Auto-actualización incorporada**: Los hooks se actualizan automáticamente en cada commit.
|
|
@@ -405,16 +388,15 @@ WSL Terminal
|
|
|
405
388
|
```
|
|
406
389
|
claude-git-hooks/
|
|
407
390
|
├── bin/
|
|
408
|
-
│ └── claude-hooks
|
|
391
|
+
│ └── claude-hooks # CLI principal con verificación completa
|
|
409
392
|
├── templates/
|
|
410
393
|
│ ├── pre-commit # Hook de análisis de código
|
|
411
394
|
│ ├── prepare-commit-msg # Hook de generación de mensajes
|
|
412
395
|
│ ├── CLAUDE_PRE_COMMIT.md # Pautas estándar
|
|
413
396
|
│ └── CLAUDE_PRE_COMMIT_SONAR.md # Pautas SonarQube
|
|
414
|
-
├── setup-wsl.sh # Script de instalación local (legacy)
|
|
415
397
|
├── package.json # Configuración NPM
|
|
416
398
|
├── README.md # Este archivo
|
|
417
|
-
├──
|
|
399
|
+
├── CHANGELOG.md # Historial de versiones
|
|
418
400
|
└── PUBLISH.md # Guía de publicación
|
|
419
401
|
```
|
|
420
402
|
|
package/bin/claude-hooks
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
const { execSync } = require('child_process');
|
|
3
|
+
const { execSync, spawn } = require('child_process');
|
|
4
4
|
const fs = require('fs');
|
|
5
5
|
const path = require('path');
|
|
6
6
|
const os = require('os');
|
|
7
|
+
const readline = require('readline');
|
|
8
|
+
const https = require('https');
|
|
7
9
|
|
|
8
10
|
// Colores para output
|
|
9
11
|
const colors = {
|
|
@@ -35,6 +37,206 @@ function warning(message) {
|
|
|
35
37
|
log(`⚠️ ${message}`, 'yellow');
|
|
36
38
|
}
|
|
37
39
|
|
|
40
|
+
// Función para leer contraseña de forma segura
|
|
41
|
+
function readPassword(prompt) {
|
|
42
|
+
return new Promise((resolve) => {
|
|
43
|
+
const rl = readline.createInterface({
|
|
44
|
+
input: process.stdin,
|
|
45
|
+
output: process.stdout
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// Deshabilitar echo
|
|
49
|
+
rl.stdoutMuted = true;
|
|
50
|
+
rl._writeToOutput = function _writeToOutput(stringToWrite) {
|
|
51
|
+
if (rl.stdoutMuted)
|
|
52
|
+
rl.output.write("*");
|
|
53
|
+
else
|
|
54
|
+
rl.output.write(stringToWrite);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
rl.question(prompt, (password) => {
|
|
58
|
+
rl.close();
|
|
59
|
+
console.log(); // Nueva línea
|
|
60
|
+
resolve(password);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Verificar si la contraseña sudo es correcta
|
|
66
|
+
function testSudoPassword(password) {
|
|
67
|
+
try {
|
|
68
|
+
execSync('echo "' + password + '" | sudo -S true', {
|
|
69
|
+
stdio: 'ignore',
|
|
70
|
+
timeout: 5000
|
|
71
|
+
});
|
|
72
|
+
return true;
|
|
73
|
+
} catch (e) {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Instalar paquete con sudo automático
|
|
79
|
+
function installPackage(packageName, sudoPassword = null) {
|
|
80
|
+
try {
|
|
81
|
+
if (sudoPassword) {
|
|
82
|
+
if (os.platform() === 'linux') {
|
|
83
|
+
execSync(`echo "${sudoPassword}" | sudo -S apt-get update && echo "${sudoPassword}" | sudo -S apt-get install -y ${packageName}`, {
|
|
84
|
+
stdio: 'inherit'
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
} else {
|
|
88
|
+
if (os.platform() === 'linux') {
|
|
89
|
+
execSync(`sudo apt-get update && sudo apt-get install -y ${packageName}`, {
|
|
90
|
+
stdio: 'inherit'
|
|
91
|
+
});
|
|
92
|
+
} else if (os.platform() === 'darwin') {
|
|
93
|
+
execSync(`brew install ${packageName}`, {
|
|
94
|
+
stdio: 'inherit'
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return true;
|
|
99
|
+
} catch (e) {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Sistema de entretenimiento
|
|
105
|
+
class Entertainment {
|
|
106
|
+
static jokes = [
|
|
107
|
+
"¿Por qué los programadores prefieren el modo oscuro? Porque la luz atrae bugs!",
|
|
108
|
+
"Un QA entra a un bar. Pide 1 cerveza. Pide 0 cervezas. Pide -1 cervezas.",
|
|
109
|
+
"¿Cuál es el lenguaje favorito de los piratas? R!",
|
|
110
|
+
"Hay 10 tipos de personas: las que entienden binario y las que no.",
|
|
111
|
+
"¿Por qué los programadores confunden Halloween con Navidad? Porque Oct 31 = Dec 25",
|
|
112
|
+
"¿Qué le dice un bit al otro? Nos vemos en el bus!",
|
|
113
|
+
"¿Por qué Java y C++ no se llevan bien? Porque tienen diferentes puntos de vista sobre los punteros.",
|
|
114
|
+
"Mi código no tiene bugs, solo features no documentadas."
|
|
115
|
+
];
|
|
116
|
+
|
|
117
|
+
static async getJoke() {
|
|
118
|
+
return new Promise((resolve) => {
|
|
119
|
+
// Intentar obtener chiste de API
|
|
120
|
+
const req = https.get('https://icanhazdadjoke.com/', {
|
|
121
|
+
headers: { 'Accept': 'text/plain' },
|
|
122
|
+
timeout: 3000
|
|
123
|
+
}, (res) => {
|
|
124
|
+
let data = '';
|
|
125
|
+
res.on('data', chunk => data += chunk);
|
|
126
|
+
res.on('end', () => resolve(data.trim()));
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
req.on('error', () => {
|
|
130
|
+
// Si falla, usar chiste local
|
|
131
|
+
const randomJoke = this.jokes[Math.floor(Math.random() * this.jokes.length)];
|
|
132
|
+
resolve(randomJoke);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
req.on('timeout', () => {
|
|
136
|
+
req.abort();
|
|
137
|
+
const randomJoke = this.jokes[Math.floor(Math.random() * this.jokes.length)];
|
|
138
|
+
resolve(randomJoke);
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
static async showSpinner(promise, message = 'Procesando') {
|
|
144
|
+
const spinners = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
145
|
+
let spinnerIndex = 0;
|
|
146
|
+
let jokeCountdown = 10;
|
|
147
|
+
let currentJoke = this.jokes[Math.floor(Math.random() * this.jokes.length)];
|
|
148
|
+
let isFinished = false;
|
|
149
|
+
let isFirstRender = true;
|
|
150
|
+
|
|
151
|
+
// Obtener primer chiste de la API sin bloquear
|
|
152
|
+
this.getJoke().then(joke => {
|
|
153
|
+
if (!isFinished) currentJoke = joke;
|
|
154
|
+
}).catch(() => {}); // Si falla, mantener el local
|
|
155
|
+
|
|
156
|
+
// Ocultar cursor
|
|
157
|
+
process.stdout.write('\x1B[?25l');
|
|
158
|
+
|
|
159
|
+
// Reservar espacio para las 3 líneas
|
|
160
|
+
process.stdout.write('\n\n\n');
|
|
161
|
+
|
|
162
|
+
const interval = setInterval(() => {
|
|
163
|
+
if (isFinished) {
|
|
164
|
+
clearInterval(interval);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
spinnerIndex++;
|
|
169
|
+
|
|
170
|
+
// Actualizar countdown cada segundo (10 iteraciones de 100ms)
|
|
171
|
+
if (spinnerIndex % 10 === 0) {
|
|
172
|
+
jokeCountdown--;
|
|
173
|
+
|
|
174
|
+
// Renovar chiste cada 10 segundos
|
|
175
|
+
if (jokeCountdown <= 0) {
|
|
176
|
+
this.getJoke().then(joke => {
|
|
177
|
+
if (!isFinished) currentJoke = joke;
|
|
178
|
+
}).catch(() => {
|
|
179
|
+
if (!isFinished) {
|
|
180
|
+
currentJoke = this.jokes[Math.floor(Math.random() * this.jokes.length)];
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
jokeCountdown = 10;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Siempre volver exactamente 3 líneas arriba
|
|
188
|
+
process.stdout.write('\x1B[3A');
|
|
189
|
+
|
|
190
|
+
// Renderizar las 3 líneas desde el inicio
|
|
191
|
+
const spinner = spinners[spinnerIndex % spinners.length];
|
|
192
|
+
|
|
193
|
+
// Línea 1: Spinner
|
|
194
|
+
process.stdout.write('\r\x1B[2K' + `${colors.yellow}${spinner} ${message}${colors.reset}\n`);
|
|
195
|
+
|
|
196
|
+
// Línea 2: Chiste
|
|
197
|
+
process.stdout.write('\r\x1B[2K' + `${colors.green}🎭 ${currentJoke}${colors.reset}\n`);
|
|
198
|
+
|
|
199
|
+
// Línea 3: Countdown
|
|
200
|
+
process.stdout.write('\r\x1B[2K' + `${colors.yellow}⏱️ Próximo chiste en: ${jokeCountdown}s${colors.reset}\n`);
|
|
201
|
+
}, 100);
|
|
202
|
+
|
|
203
|
+
try {
|
|
204
|
+
const result = await promise;
|
|
205
|
+
isFinished = true;
|
|
206
|
+
clearInterval(interval);
|
|
207
|
+
|
|
208
|
+
// Limpiar exactamente 3 líneas completamente
|
|
209
|
+
process.stdout.write('\x1B[3A'); // Subir 3 líneas
|
|
210
|
+
process.stdout.write('\r\x1B[2K'); // Limpiar línea 1
|
|
211
|
+
process.stdout.write('\n\r\x1B[2K'); // Bajar y limpiar línea 2
|
|
212
|
+
process.stdout.write('\n\r\x1B[2K'); // Bajar y limpiar línea 3
|
|
213
|
+
process.stdout.write('\x1B[2A'); // Subir 2 líneas para quedar en la primera
|
|
214
|
+
process.stdout.write('\r'); // Ir al inicio de línea
|
|
215
|
+
|
|
216
|
+
// Mostrar cursor
|
|
217
|
+
process.stdout.write('\x1B[?25h');
|
|
218
|
+
|
|
219
|
+
return result;
|
|
220
|
+
} catch (error) {
|
|
221
|
+
isFinished = true;
|
|
222
|
+
clearInterval(interval);
|
|
223
|
+
|
|
224
|
+
// Limpiar exactamente 3 líneas completamente
|
|
225
|
+
process.stdout.write('\x1B[3A'); // Subir 3 líneas
|
|
226
|
+
process.stdout.write('\r\x1B[2K'); // Limpiar línea 1
|
|
227
|
+
process.stdout.write('\n\r\x1B[2K'); // Bajar y limpiar línea 2
|
|
228
|
+
process.stdout.write('\n\r\x1B[2K'); // Bajar y limpiar línea 3
|
|
229
|
+
process.stdout.write('\x1B[2A'); // Subir 2 líneas para quedar en la primera
|
|
230
|
+
process.stdout.write('\r'); // Ir al inicio de línea
|
|
231
|
+
|
|
232
|
+
// Mostrar cursor
|
|
233
|
+
process.stdout.write('\x1B[?25h');
|
|
234
|
+
|
|
235
|
+
throw error;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
38
240
|
// Verificar si estamos en un repositorio git
|
|
39
241
|
function checkGitRepo() {
|
|
40
242
|
try {
|
|
@@ -51,13 +253,33 @@ function getTemplatesPath() {
|
|
|
51
253
|
}
|
|
52
254
|
|
|
53
255
|
// Comando install
|
|
54
|
-
function install(args) {
|
|
256
|
+
async function install(args) {
|
|
55
257
|
if (!checkGitRepo()) {
|
|
56
258
|
error('No estás en un repositorio Git. Por favor, ejecuta este comando desde la raíz de un repositorio.');
|
|
57
259
|
}
|
|
58
260
|
|
|
59
261
|
info('Instalando Claude Git Hooks...');
|
|
60
262
|
|
|
263
|
+
// Solicitar contraseña sudo al inicio si es necesario
|
|
264
|
+
let sudoPassword = null;
|
|
265
|
+
if (os.platform() === 'linux') {
|
|
266
|
+
const needsInstall = await checkIfInstallationNeeded();
|
|
267
|
+
if (needsInstall) {
|
|
268
|
+
info('Para la instalación automática de dependencias se necesita acceso sudo, por favor ingrese contraseña');
|
|
269
|
+
sudoPassword = await readPassword('Introduce tu contraseña de Ubuntu para sudo: ');
|
|
270
|
+
|
|
271
|
+
if (sudoPassword && !testSudoPassword(sudoPassword)) {
|
|
272
|
+
warning('Contraseña incorrecta. Continuando sin instalación automática.');
|
|
273
|
+
sudoPassword = null;
|
|
274
|
+
} else if (sudoPassword) {
|
|
275
|
+
success('Contraseña verificada. Procediendo con instalación automática.');
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Verificar dependencias con instalación automática
|
|
281
|
+
await checkAndInstallDependencies(sudoPassword);
|
|
282
|
+
|
|
61
283
|
const templatesPath = getTemplatesPath();
|
|
62
284
|
const hooksPath = '.git/hooks';
|
|
63
285
|
|
|
@@ -91,13 +313,15 @@ function install(args) {
|
|
|
91
313
|
guidelines.forEach(guideline => {
|
|
92
314
|
if (!fs.existsSync(guideline)) {
|
|
93
315
|
const sourcePath = path.join(templatesPath, guideline);
|
|
94
|
-
fs.
|
|
95
|
-
|
|
316
|
+
if (fs.existsSync(sourcePath)) {
|
|
317
|
+
fs.copyFileSync(sourcePath, guideline);
|
|
318
|
+
success(`${guideline} creado`);
|
|
319
|
+
}
|
|
96
320
|
}
|
|
97
321
|
});
|
|
98
322
|
|
|
99
|
-
//
|
|
100
|
-
|
|
323
|
+
// Configurar Git
|
|
324
|
+
configureGit();
|
|
101
325
|
|
|
102
326
|
success('¡Claude Git Hooks instalado exitosamente! 🎉');
|
|
103
327
|
console.log('\nUso:');
|
|
@@ -106,31 +330,198 @@ function install(args) {
|
|
|
106
330
|
console.log('\nPara más opciones: claude-hooks --help');
|
|
107
331
|
}
|
|
108
332
|
|
|
109
|
-
// Verificar dependencias
|
|
110
|
-
function
|
|
111
|
-
info('
|
|
333
|
+
// Verificar dependencias completas (como setup-wsl.sh)
|
|
334
|
+
async function checkAndInstallDependencies(sudoPassword = null) {
|
|
335
|
+
info('Verificando dependencias del sistema...');
|
|
336
|
+
|
|
337
|
+
// Verificar Node.js
|
|
338
|
+
try {
|
|
339
|
+
const nodeVersion = execSync('node --version', { encoding: 'utf8' }).trim();
|
|
340
|
+
success(`Node.js ${nodeVersion}`);
|
|
341
|
+
} catch (e) {
|
|
342
|
+
error('Node.js no está instalado. Instala Node.js e intenta de nuevo.');
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Verificar npm
|
|
346
|
+
try {
|
|
347
|
+
const npmVersion = execSync('npm --version', { encoding: 'utf8' }).trim();
|
|
348
|
+
success(`npm ${npmVersion}`);
|
|
349
|
+
} catch (e) {
|
|
350
|
+
error('npm no está instalado.');
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Verificar e instalar jq
|
|
354
|
+
try {
|
|
355
|
+
const jqVersion = execSync('jq --version', { encoding: 'utf8' }).trim();
|
|
356
|
+
success(`jq ${jqVersion}`);
|
|
357
|
+
} catch (e) {
|
|
358
|
+
warning('jq no está instalado. Instalando...');
|
|
359
|
+
if (installPackage('jq', sudoPassword)) {
|
|
360
|
+
success('jq instalado correctamente');
|
|
361
|
+
} else {
|
|
362
|
+
warning('No se pudo instalar jq automáticamente');
|
|
363
|
+
if (os.platform() === 'linux') {
|
|
364
|
+
console.log('Instálalo manualmente con: sudo apt install jq');
|
|
365
|
+
} else if (os.platform() === 'darwin') {
|
|
366
|
+
console.log('Instálalo manualmente con: brew install jq');
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Verificar e instalar curl
|
|
372
|
+
try {
|
|
373
|
+
const curlVersion = execSync('curl --version', { encoding: 'utf8' }).split('\n')[0];
|
|
374
|
+
success(`curl ${curlVersion.split(' ')[1]}`);
|
|
375
|
+
} catch (e) {
|
|
376
|
+
warning('curl no está instalado. Instalando...');
|
|
377
|
+
if (installPackage('curl', sudoPassword)) {
|
|
378
|
+
success('curl instalado correctamente');
|
|
379
|
+
} else {
|
|
380
|
+
warning('No se pudo instalar curl automáticamente');
|
|
381
|
+
if (os.platform() === 'linux') {
|
|
382
|
+
console.log('Instálalo manualmente con: sudo apt install curl');
|
|
383
|
+
} else if (os.platform() === 'darwin') {
|
|
384
|
+
console.log('Instálalo manualmente con: brew install curl');
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Verificar Git
|
|
390
|
+
try {
|
|
391
|
+
const gitVersion = execSync('git --version', { encoding: 'utf8' }).trim();
|
|
392
|
+
success(`${gitVersion}`);
|
|
393
|
+
} catch (e) {
|
|
394
|
+
error('Git no está instalado. Instala Git e intenta de nuevo.');
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// Verificar herramientas Unix estándar
|
|
398
|
+
const unixTools = ['sed', 'awk', 'grep', 'head', 'tail', 'stat', 'tput'];
|
|
399
|
+
const missingTools = [];
|
|
400
|
+
|
|
401
|
+
unixTools.forEach(tool => {
|
|
402
|
+
try {
|
|
403
|
+
execSync(`which ${tool}`, { stdio: 'ignore' });
|
|
404
|
+
} catch (e) {
|
|
405
|
+
missingTools.push(tool);
|
|
406
|
+
}
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
if (missingTools.length === 0) {
|
|
410
|
+
success('Herramientas Unix estándar verificadas');
|
|
411
|
+
} else {
|
|
412
|
+
error(`Faltan herramientas Unix estándar: ${missingTools.join(', ')}`);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// Verificar e instalar Claude CLI
|
|
416
|
+
await checkAndInstallClaude();
|
|
417
|
+
|
|
418
|
+
// Verificar autenticación de Claude
|
|
419
|
+
await checkClaudeAuth();
|
|
420
|
+
|
|
421
|
+
// Limpiar contraseña de memoria
|
|
422
|
+
sudoPassword = null;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// Verificar si necesitamos instalar dependencias
|
|
426
|
+
async function checkIfInstallationNeeded() {
|
|
427
|
+
const dependencies = ['jq', 'curl'];
|
|
428
|
+
|
|
429
|
+
for (const dep of dependencies) {
|
|
430
|
+
try {
|
|
431
|
+
execSync(`which ${dep}`, { stdio: 'ignore' });
|
|
432
|
+
} catch (e) {
|
|
433
|
+
return true; // Necesita instalación
|
|
434
|
+
}
|
|
435
|
+
}
|
|
112
436
|
|
|
113
437
|
// Verificar Claude CLI
|
|
114
438
|
try {
|
|
115
439
|
execSync('claude --version', { stdio: 'ignore' });
|
|
116
|
-
success('Claude CLI encontrado');
|
|
117
440
|
} catch (e) {
|
|
118
|
-
|
|
119
|
-
console.log('Instálalo con: npm install -g @anthropic-ai/claude-cli');
|
|
120
|
-
console.log('Luego ejecuta: claude auth login');
|
|
441
|
+
return true; // Necesita instalación de Claude
|
|
121
442
|
}
|
|
122
443
|
|
|
123
|
-
|
|
444
|
+
return false;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// Verificar e instalar Claude CLI
|
|
448
|
+
async function checkAndInstallClaude() {
|
|
124
449
|
try {
|
|
125
|
-
execSync('
|
|
126
|
-
success('
|
|
450
|
+
execSync('claude --version', { stdio: 'ignore' });
|
|
451
|
+
success('Claude CLI detectado');
|
|
452
|
+
} catch (e) {
|
|
453
|
+
info('Claude CLI no detectado. Instalando...');
|
|
454
|
+
try {
|
|
455
|
+
execSync('npm install -g @anthropic-ai/claude-cli', { stdio: 'inherit' });
|
|
456
|
+
success('Claude CLI instalado correctamente');
|
|
457
|
+
} catch (installError) {
|
|
458
|
+
error('Error al instalar Claude CLI. Instálalo manualmente: npm install -g @anthropic-ai/claude-cli');
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// Verificar autenticación de Claude con entretenimiento
|
|
464
|
+
async function checkClaudeAuth() {
|
|
465
|
+
info('Verificando autenticación de Claude...');
|
|
466
|
+
|
|
467
|
+
// Usar spawn para no bloquear, pero con stdio: 'ignore' como el original
|
|
468
|
+
const authPromise = new Promise((resolve, reject) => {
|
|
469
|
+
const child = spawn('claude', ['auth', 'status'], {
|
|
470
|
+
stdio: 'ignore', // Igual que el original
|
|
471
|
+
detached: false,
|
|
472
|
+
windowsHide: true
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
// Timeout manual ya que spawn no tiene timeout nativo
|
|
476
|
+
const timeout = setTimeout(() => {
|
|
477
|
+
child.kill();
|
|
478
|
+
reject(new Error('timeout'));
|
|
479
|
+
}, 120000); // 2 minutos
|
|
480
|
+
|
|
481
|
+
child.on('exit', (code) => {
|
|
482
|
+
clearTimeout(timeout);
|
|
483
|
+
if (code === 0) {
|
|
484
|
+
resolve('success');
|
|
485
|
+
} else {
|
|
486
|
+
reject(new Error('not_authenticated'));
|
|
487
|
+
}
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
child.on('error', (err) => {
|
|
491
|
+
clearTimeout(timeout);
|
|
492
|
+
reject(err);
|
|
493
|
+
});
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
try {
|
|
497
|
+
await Entertainment.showSpinner(authPromise, 'Verificando autenticación de Claude');
|
|
498
|
+
success('Autenticado en Claude');
|
|
127
499
|
} catch (e) {
|
|
128
|
-
warning('
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
500
|
+
warning('No estás autenticado en Claude');
|
|
501
|
+
console.log('Ejecuta: claude auth login');
|
|
502
|
+
console.log('Después vuelve a ejecutar este comando');
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// Configurar Git (line endings, etc.)
|
|
507
|
+
function configureGit() {
|
|
508
|
+
info('Configurando Git...');
|
|
509
|
+
|
|
510
|
+
try {
|
|
511
|
+
// Configurar line endings para WSL
|
|
512
|
+
execSync('git config core.autocrlf input', { stdio: 'ignore' });
|
|
513
|
+
success('Line endings configurados para WSL (core.autocrlf = input)');
|
|
514
|
+
|
|
515
|
+
// Intentar configurar en Windows a través de PowerShell
|
|
516
|
+
try {
|
|
517
|
+
execSync('powershell.exe -Command "git config core.autocrlf true"', { stdio: 'ignore' });
|
|
518
|
+
success('Line endings configurados para Windows (core.autocrlf = true)');
|
|
519
|
+
} catch (psError) {
|
|
520
|
+
info('No se pudo configurar automáticamente en Windows');
|
|
133
521
|
}
|
|
522
|
+
|
|
523
|
+
} catch (e) {
|
|
524
|
+
warning('Error al configurar Git');
|
|
134
525
|
}
|
|
135
526
|
}
|
|
136
527
|
|
|
@@ -315,35 +706,42 @@ Más información: https://github.com/pablorovito/claude-git-hooks
|
|
|
315
706
|
}
|
|
316
707
|
|
|
317
708
|
// Main
|
|
318
|
-
|
|
319
|
-
const
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
install
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
uninstall
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
enable
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
disable
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
status
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
709
|
+
async function main() {
|
|
710
|
+
const args = process.argv.slice(2);
|
|
711
|
+
const command = args[0];
|
|
712
|
+
|
|
713
|
+
switch (command) {
|
|
714
|
+
case 'install':
|
|
715
|
+
await install(args.slice(1));
|
|
716
|
+
break;
|
|
717
|
+
case 'uninstall':
|
|
718
|
+
uninstall();
|
|
719
|
+
break;
|
|
720
|
+
case 'enable':
|
|
721
|
+
enable(args[1]);
|
|
722
|
+
break;
|
|
723
|
+
case 'disable':
|
|
724
|
+
disable(args[1]);
|
|
725
|
+
break;
|
|
726
|
+
case 'set-mode':
|
|
727
|
+
setMode(args[1]);
|
|
728
|
+
break;
|
|
729
|
+
case 'status':
|
|
730
|
+
status();
|
|
731
|
+
break;
|
|
732
|
+
case 'help':
|
|
733
|
+
case '--help':
|
|
734
|
+
case '-h':
|
|
735
|
+
case undefined:
|
|
736
|
+
showHelp();
|
|
737
|
+
break;
|
|
738
|
+
default:
|
|
739
|
+
error(`Comando desconocido: ${command}`);
|
|
740
|
+
showHelp();
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
// Ejecutar main
|
|
745
|
+
main().catch(err => {
|
|
746
|
+
error(`Error inesperado: ${err.message}`);
|
|
747
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-git-hooks",
|
|
3
|
-
"version": "1.1
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "Git hooks con Claude CLI para análisis de código y generación automática de mensajes de commit",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
"lib/",
|
|
36
36
|
"templates/",
|
|
37
37
|
"README.md",
|
|
38
|
+
"CHANGELOG.md",
|
|
38
39
|
"LICENSE"
|
|
39
40
|
]
|
|
40
41
|
}
|