@asaro/git-cli 1.0.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.
- package/README.md +189 -0
- package/dist/adapters/git/SimpleGitAdapter.js +126 -0
- package/dist/adapters/github/GitHubAPIAdapter.js +243 -0
- package/dist/adapters/storage/FileSecureStorageAdapter.js +67 -0
- package/dist/adapters/ui/ConsoleUIAdapter.js +91 -0
- package/dist/commands/BaseCommand.js +11 -0
- package/dist/commands/BranchesCommand.js +152 -0
- package/dist/commands/Command.js +1 -0
- package/dist/commands/CommitCommand.js +85 -0
- package/dist/commands/IntegrationCommand.js +151 -0
- package/dist/commands/LogCommand.js +42 -0
- package/dist/commands/PullCommand.js +55 -0
- package/dist/commands/PushCommand.js +227 -0
- package/dist/commands/RepositoryCommand.js +268 -0
- package/dist/commands/StashCommand.js +140 -0
- package/dist/commands/StatusCommand.js +66 -0
- package/dist/commands/github/BaseGitHubCommand.js +17 -0
- package/dist/commands/github/GitHubAuthCommand.js +48 -0
- package/dist/commands/github/GitHubMenuCommand.js +57 -0
- package/dist/commands/github/GitHubReposCommand.js +210 -0
- package/dist/commands/github/GitHubWorkflowsCommand.js +243 -0
- package/dist/commit.js +98 -0
- package/dist/core/errors/GitError.js +19 -0
- package/dist/core/errors/GitHubError.js +36 -0
- package/dist/core/ports/GitHubPort.js +1 -0
- package/dist/core/ports/GitPort.js +1 -0
- package/dist/core/ports/SecureStoragePort.js +1 -0
- package/dist/core/ports/UIPort.js +1 -0
- package/dist/core/services/GitHubService.js +95 -0
- package/dist/core/services/GitService.js +44 -0
- package/dist/estado.js +97 -0
- package/dist/factories/CommandFactory.js +51 -0
- package/dist/index.js +71 -0
- package/dist/integracion.js +152 -0
- package/dist/ramas.js +178 -0
- package/dist/remoto.js +129 -0
- package/dist/repositorio.js +159 -0
- package/dist/stash.js +169 -0
- package/dist/utils.js +49 -0
- package/package.json +53 -0
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { BaseCommand } from './BaseCommand.js';
|
|
2
|
+
import { simpleGit } from 'simple-git';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
export class PushCommand extends BaseCommand {
|
|
5
|
+
gitHubService;
|
|
6
|
+
constructor(gitPort, uiPort, gitHubService) {
|
|
7
|
+
super(gitPort, uiPort);
|
|
8
|
+
this.gitHubService = gitHubService;
|
|
9
|
+
}
|
|
10
|
+
async execute() {
|
|
11
|
+
try {
|
|
12
|
+
const isRepo = await this.gitPort.isRepository();
|
|
13
|
+
if (!isRepo) {
|
|
14
|
+
this.uiPort.error(' ✗ Este directorio no es un repositorio Git.');
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const spin = this.uiPort.spinner('Verificando estado...');
|
|
18
|
+
const status = await this.gitPort.getStatus();
|
|
19
|
+
let remotos = await this.gitPort.getRemotes();
|
|
20
|
+
spin.stop();
|
|
21
|
+
if (remotos.length === 0) {
|
|
22
|
+
// No hay remotos: preguntar al usuario
|
|
23
|
+
const accion = await this.uiPort.select('No hay remotos configurados. ¿Qué deseas hacer?', [
|
|
24
|
+
{ name: 'Crear nuevo repositorio en GitHub', value: 'nuevo' },
|
|
25
|
+
{ name: 'Elegir repositorio existente de GitHub', value: 'existente' },
|
|
26
|
+
{ name: 'Agregar remoto manualmente', value: 'manual' },
|
|
27
|
+
{ name: 'Volver', value: 'volver' },
|
|
28
|
+
]);
|
|
29
|
+
if (accion === 'volver') {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
else if (accion === 'manual') {
|
|
33
|
+
// Llamar a la función de agregar remoto
|
|
34
|
+
await this.agregarRemotoManual();
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
// Verificar autenticación primero
|
|
38
|
+
const isAuth = await this.gitHubService.isAuthenticated();
|
|
39
|
+
if (!isAuth) {
|
|
40
|
+
this.uiPort.error(' ✗ No estás autenticado con GitHub.');
|
|
41
|
+
this.uiPort.info(' Ve al menú "🌐 Gestión GitHub" primero para iniciar sesión.');
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (accion === 'nuevo') {
|
|
45
|
+
await this.crearYConfigurarRepoGitHub();
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
await this.elegirYConfigurarRepoExistente();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
// Volver a obtener remotos después de configurar
|
|
52
|
+
remotos = await this.gitPort.getRemotes();
|
|
53
|
+
if (remotos.length === 0) {
|
|
54
|
+
// Si sigue sin haber remotos, no continuar
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
const c = this.uiColors;
|
|
59
|
+
const rama = status.current ?? 'main';
|
|
60
|
+
this.uiPort.log('');
|
|
61
|
+
this.uiPort.log(c.titulo(' ↑ Subir cambios (push)'));
|
|
62
|
+
this.uiPort.separator();
|
|
63
|
+
this.uiPort.log(` Rama actual: ${c.rama(rama)}`);
|
|
64
|
+
if (status.ahead > 0) {
|
|
65
|
+
this.uiPort.log(c.aviso(` ${status.ahead} commit(s) pendientes de subir`));
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
this.uiPort.log(c.tenue(' No hay commits nuevos para subir.'));
|
|
69
|
+
}
|
|
70
|
+
this.uiPort.separator();
|
|
71
|
+
let remoto = 'origin';
|
|
72
|
+
if (remotos.length > 1) {
|
|
73
|
+
remoto = await this.uiPort.select('¿A qué remoto hacer push?', remotos.map((r) => ({ name: r.name, value: r.name })));
|
|
74
|
+
}
|
|
75
|
+
const modo = await this.uiPort.select('Modo de push:', [
|
|
76
|
+
{ name: `Subir rama actual (${rama})`, value: 'normal' },
|
|
77
|
+
{ name: 'Subir y crear rama en remoto (--set-upstream)', value: 'upstream' },
|
|
78
|
+
{ name: 'Forzar push (--force-with-lease)', value: 'force' },
|
|
79
|
+
{ name: 'Subir todos los tags', value: 'tags' },
|
|
80
|
+
]);
|
|
81
|
+
const confirmar = await this.uiPort.confirm(`¿Confirmar push a ${remoto}/${rama}?`, true);
|
|
82
|
+
if (!confirmar) {
|
|
83
|
+
this.uiPort.warning(' Push cancelado.\n');
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const spinPush = this.uiPort.spinner('Subiendo cambios...');
|
|
87
|
+
switch (modo) {
|
|
88
|
+
case 'upstream':
|
|
89
|
+
await this.gitPort.push(remoto, rama, [remoto, rama, '--set-upstream']);
|
|
90
|
+
break;
|
|
91
|
+
case 'force':
|
|
92
|
+
await this.gitPort.push(remoto, rama, [remoto, rama, '--force-with-lease']);
|
|
93
|
+
break;
|
|
94
|
+
case 'tags':
|
|
95
|
+
await this.gitPort.push(remoto, rama, [remoto, '--tags']);
|
|
96
|
+
break;
|
|
97
|
+
default:
|
|
98
|
+
await this.gitPort.push(remoto, rama);
|
|
99
|
+
}
|
|
100
|
+
spinPush.stop();
|
|
101
|
+
this.uiPort.success(`\n ✓ Push exitoso → ${remoto}/${rama}\n`);
|
|
102
|
+
}
|
|
103
|
+
catch (err) {
|
|
104
|
+
this.uiPort.error(` ✗ Error: ${err.message}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
async agregarRemotoManual() {
|
|
108
|
+
const nombre = await this.uiPort.input('Nombre del remoto:', {
|
|
109
|
+
default: 'origin',
|
|
110
|
+
validate: (v) => v.trim().length > 0 || 'Nombre requerido.',
|
|
111
|
+
});
|
|
112
|
+
const url = await this.uiPort.input('URL del remoto:', {
|
|
113
|
+
validate: (v) => v.trim().length > 0 || 'URL requerida.',
|
|
114
|
+
});
|
|
115
|
+
const spin = this.uiPort.spinner('Agregando remoto...');
|
|
116
|
+
try {
|
|
117
|
+
await this.gitPort.addRemote(nombre, url);
|
|
118
|
+
spin.stop();
|
|
119
|
+
this.uiPort.success(` ✓ Remoto "${nombre}" agregado.`);
|
|
120
|
+
}
|
|
121
|
+
catch (err) {
|
|
122
|
+
spin.stop();
|
|
123
|
+
this.uiPort.error(` ✗ Error: ${err.message}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
async crearYConfigurarRepoGitHub() {
|
|
127
|
+
const c = this.uiColors;
|
|
128
|
+
const nombreCarpeta = path.basename(process.cwd());
|
|
129
|
+
this.uiPort.log('');
|
|
130
|
+
this.uiPort.log(c.titulo(' 🚀 Crear repo en GitHub'));
|
|
131
|
+
this.uiPort.separator();
|
|
132
|
+
// Usar el nombre de la carpeta actual por defecto
|
|
133
|
+
const nombreRepo = await this.uiPort.input('Nombre del repositorio:', {
|
|
134
|
+
default: nombreCarpeta,
|
|
135
|
+
validate: (v) => v.trim().length > 0 || 'El nombre no puede estar vacío.',
|
|
136
|
+
});
|
|
137
|
+
const descripcion = await this.uiPort.input('Descripción (opcional):', {
|
|
138
|
+
default: '',
|
|
139
|
+
});
|
|
140
|
+
const visibilidad = await this.uiPort.select('Visibilidad:', [
|
|
141
|
+
{ name: 'Público', value: 'public' },
|
|
142
|
+
{ name: 'Privado', value: 'private' },
|
|
143
|
+
]);
|
|
144
|
+
const cwd = process.cwd();
|
|
145
|
+
this.uiPort.separator();
|
|
146
|
+
this.uiPort.log(c.titulo(' Resumen:'));
|
|
147
|
+
this.uiPort.log(c.info(` Carpeta: ${cwd}`));
|
|
148
|
+
this.uiPort.log(c.info(` Repo: ${nombreRepo}`));
|
|
149
|
+
this.uiPort.log(c.info(` Visibilidad: ${visibilidad}`));
|
|
150
|
+
if (descripcion.trim())
|
|
151
|
+
this.uiPort.log(c.tenue(` Descripción: ${descripcion}`));
|
|
152
|
+
this.uiPort.separator();
|
|
153
|
+
const confirmar = await this.uiPort.confirm('¿Confirmar y continuar?', true);
|
|
154
|
+
if (!confirmar) {
|
|
155
|
+
this.uiPort.warning(' Operación cancelada.');
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
// Paso 1: Crear repo en GitHub (sin autoInit para no conflictuar con local)
|
|
159
|
+
const spinGH = this.uiPort.spinner('Creando repositorio en GitHub...');
|
|
160
|
+
let repoCreado;
|
|
161
|
+
try {
|
|
162
|
+
repoCreado = await this.gitHubService.createRepo({
|
|
163
|
+
name: nombreRepo.trim(),
|
|
164
|
+
description: descripcion.trim() || undefined,
|
|
165
|
+
visibility: visibilidad,
|
|
166
|
+
autoInit: false,
|
|
167
|
+
});
|
|
168
|
+
spinGH.stop();
|
|
169
|
+
this.uiPort.success(c.exito(` ✓ Repo creado en GitHub: ${repoCreado.htmlUrl}`));
|
|
170
|
+
}
|
|
171
|
+
catch (err) {
|
|
172
|
+
spinGH.stop();
|
|
173
|
+
this.uiPort.error(` ✗ Error al crear repo: ${err.message}`);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
// Paso 2: Agregar remoto
|
|
177
|
+
const spinRemote = this.uiPort.spinner('Configurando remoto...');
|
|
178
|
+
try {
|
|
179
|
+
const git = simpleGit(cwd);
|
|
180
|
+
// Elegir método: HTTPS o SSH
|
|
181
|
+
const metodo = await this.uiPort.select('Método de conexión:', [
|
|
182
|
+
{ name: 'HTTPS', value: 'https' },
|
|
183
|
+
{ name: 'SSH', value: 'ssh' },
|
|
184
|
+
]);
|
|
185
|
+
const url = metodo === 'https' ? repoCreado.cloneUrl : repoCreado.sshUrl;
|
|
186
|
+
await git.addRemote('origin', url);
|
|
187
|
+
spinRemote.stop();
|
|
188
|
+
this.uiPort.success(c.exito(` ✓ Remoto origin agregado: ${url}`));
|
|
189
|
+
}
|
|
190
|
+
catch (err) {
|
|
191
|
+
spinRemote.stop();
|
|
192
|
+
this.uiPort.error(` ✗ Error al configurar remoto: ${err.message}`);
|
|
193
|
+
this.uiPort.warning(' El repositorio sigue existiendo en GitHub.');
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
async elegirYConfigurarRepoExistente() {
|
|
197
|
+
const c = this.uiColors;
|
|
198
|
+
const spin = this.uiPort.spinner('Cargando repositorios...');
|
|
199
|
+
const repos = await this.gitHubService.listRepos();
|
|
200
|
+
spin.stop();
|
|
201
|
+
if (repos.length === 0) {
|
|
202
|
+
this.uiPort.warning(' No tienes repositorios en GitHub.');
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
this.uiPort.log('');
|
|
206
|
+
const repoSeleccionado = await this.uiPort.select('Selecciona un repositorio:', repos.map(r => ({
|
|
207
|
+
name: `${r.fullName}${r.archived ? ' (archivado)' : ''}`,
|
|
208
|
+
value: r
|
|
209
|
+
})));
|
|
210
|
+
// Elegir método: HTTPS o SSH
|
|
211
|
+
const metodo = await this.uiPort.select('Método de conexión:', [
|
|
212
|
+
{ name: 'HTTPS', value: 'https' },
|
|
213
|
+
{ name: 'SSH', value: 'ssh' },
|
|
214
|
+
]);
|
|
215
|
+
const url = metodo === 'https' ? repoSeleccionado.cloneUrl : repoSeleccionado.sshUrl;
|
|
216
|
+
const spinRemote = this.uiPort.spinner('Configurando remoto...');
|
|
217
|
+
try {
|
|
218
|
+
await this.gitPort.addRemote('origin', url);
|
|
219
|
+
spinRemote.stop();
|
|
220
|
+
this.uiPort.success(c.exito(` ✓ Remoto origin agregado: ${url}`));
|
|
221
|
+
}
|
|
222
|
+
catch (err) {
|
|
223
|
+
spinRemote.stop();
|
|
224
|
+
this.uiPort.error(` ✗ Error al configurar remoto: ${err.message}`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import { BaseCommand } from './BaseCommand.js';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { simpleGit } from 'simple-git';
|
|
4
|
+
export class RepositoryCommand extends BaseCommand {
|
|
5
|
+
gitHubService;
|
|
6
|
+
constructor(gitPort, uiPort, gitHubService) {
|
|
7
|
+
super(gitPort, uiPort);
|
|
8
|
+
this.gitHubService = gitHubService;
|
|
9
|
+
}
|
|
10
|
+
async execute() {
|
|
11
|
+
try {
|
|
12
|
+
const accion = await this.uiPort.select('Repositorio — ¿qué deseas hacer?', [
|
|
13
|
+
{ name: 'Clonar repositorio remoto', value: 'clonar' },
|
|
14
|
+
{ name: 'Inicializar nuevo repositorio', value: 'init' },
|
|
15
|
+
{ name: 'Crear y subir a GitHub', value: 'initGitHub' },
|
|
16
|
+
{ name: 'Ver remotos configurados', value: 'remotos' },
|
|
17
|
+
{ name: 'Agregar remoto', value: 'agregarRemoto' },
|
|
18
|
+
{ name: '← Volver al menú principal', value: 'volver' },
|
|
19
|
+
]);
|
|
20
|
+
switch (accion) {
|
|
21
|
+
case 'clonar':
|
|
22
|
+
await this.clonarRepo();
|
|
23
|
+
break;
|
|
24
|
+
case 'init':
|
|
25
|
+
await this.inicializarRepo();
|
|
26
|
+
break;
|
|
27
|
+
case 'initGitHub':
|
|
28
|
+
await this.inicializarYSubirAGitHub();
|
|
29
|
+
break;
|
|
30
|
+
case 'remotos':
|
|
31
|
+
await this.verRemotos();
|
|
32
|
+
break;
|
|
33
|
+
case 'agregarRemoto':
|
|
34
|
+
await this.agregarRemoto();
|
|
35
|
+
break;
|
|
36
|
+
case 'volver':
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
this.uiPort.error(` ✗ Error: ${err.message}`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
async clonarRepo() {
|
|
45
|
+
const c = this.uiColors;
|
|
46
|
+
this.uiPort.log('');
|
|
47
|
+
this.uiPort.log(c.titulo(' 📥 Clonar repositorio'));
|
|
48
|
+
this.uiPort.separator();
|
|
49
|
+
const url = await this.uiPort.input('URL del repositorio (HTTPS o SSH):', {
|
|
50
|
+
validate: (v) => v.trim().length > 0 || 'La URL no puede estar vacía.',
|
|
51
|
+
});
|
|
52
|
+
const nombreSugerido = url.split('/').pop()?.replace('.git', '') || 'repo';
|
|
53
|
+
const destino = await this.uiPort.input('Carpeta de destino:', {
|
|
54
|
+
default: nombreSugerido,
|
|
55
|
+
validate: (v) => v.trim().length > 0 || 'El nombre no puede estar vacío.',
|
|
56
|
+
});
|
|
57
|
+
const rama = await this.uiPort.input('Rama a clonar (dejar vacío para rama por defecto):', {
|
|
58
|
+
default: '',
|
|
59
|
+
});
|
|
60
|
+
const cwd = process.cwd();
|
|
61
|
+
const destinoAbsoluto = path.join(cwd, destino);
|
|
62
|
+
this.uiPort.separator();
|
|
63
|
+
this.uiPort.log(` URL: ${c.info(url)}`);
|
|
64
|
+
this.uiPort.log(` Carpeta: ${c.archivo(destinoAbsoluto)}`);
|
|
65
|
+
if (rama.trim())
|
|
66
|
+
this.uiPort.log(` Rama: ${c.rama(rama.trim())}`);
|
|
67
|
+
this.uiPort.separator();
|
|
68
|
+
const confirmar = await this.uiPort.confirm('¿Confirmar clonación?', true);
|
|
69
|
+
if (!confirmar) {
|
|
70
|
+
this.uiPort.warning(' Clonación cancelada.\n');
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const spinClone = this.uiPort.spinner('Clonando repositorio...');
|
|
74
|
+
try {
|
|
75
|
+
const git = simpleGit(cwd);
|
|
76
|
+
const opciones = [];
|
|
77
|
+
if (rama.trim()) {
|
|
78
|
+
opciones.push('-b', rama.trim());
|
|
79
|
+
}
|
|
80
|
+
await git.clone(url, destino, opciones);
|
|
81
|
+
spinClone.stop();
|
|
82
|
+
this.uiPort.success(`\n ✓ Repositorio clonado en: ${c.archivo(destinoAbsoluto)}\n`);
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
spinClone.stop();
|
|
86
|
+
const msg = err.message;
|
|
87
|
+
this.uiPort.error(`\n ✗ Error al clonar:\n ${msg}\n`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
async inicializarRepo() {
|
|
91
|
+
const c = this.uiColors;
|
|
92
|
+
this.uiPort.log('');
|
|
93
|
+
this.uiPort.log(c.titulo(' 🆕 Inicializar repositorio'));
|
|
94
|
+
this.uiPort.separator();
|
|
95
|
+
const esActual = await this.uiPort.select('¿Dónde inicializar?', [
|
|
96
|
+
{ name: `Directorio actual (${process.cwd()})`, value: 'actual' },
|
|
97
|
+
{ name: 'Nueva carpeta', value: 'nueva' },
|
|
98
|
+
]);
|
|
99
|
+
let destino = process.cwd();
|
|
100
|
+
if (esActual === 'nueva') {
|
|
101
|
+
const nombreCarpeta = await this.uiPort.input('Nombre de la nueva carpeta:', {
|
|
102
|
+
validate: (v) => v.trim().length > 0 || 'El nombre no puede estar vacío.',
|
|
103
|
+
});
|
|
104
|
+
destino = path.join(process.cwd(), nombreCarpeta);
|
|
105
|
+
}
|
|
106
|
+
const ramaInicial = await this.uiPort.input('Nombre de la rama inicial:', {
|
|
107
|
+
default: 'main',
|
|
108
|
+
});
|
|
109
|
+
const confirmar = await this.uiPort.confirm(`¿Inicializar repo en: ${c.archivo(destino)}?`, true);
|
|
110
|
+
if (!confirmar)
|
|
111
|
+
return;
|
|
112
|
+
const spinInit = this.uiPort.spinner('Inicializando...');
|
|
113
|
+
try {
|
|
114
|
+
const git = simpleGit(destino);
|
|
115
|
+
await git.init({ '--initial-branch': ramaInicial.trim() || 'main' });
|
|
116
|
+
spinInit.stop();
|
|
117
|
+
this.uiPort.success(`\n ✓ Repositorio inicializado en: ${c.archivo(destino)}\n`);
|
|
118
|
+
}
|
|
119
|
+
catch (err) {
|
|
120
|
+
spinInit.stop();
|
|
121
|
+
const msg = err.message;
|
|
122
|
+
this.uiPort.error(`\n ✗ Error al inicializar:\n ${msg}\n`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
async verRemotos() {
|
|
126
|
+
const isRepo = await this.gitPort.isRepository();
|
|
127
|
+
if (!isRepo) {
|
|
128
|
+
this.uiPort.error(' ✗ Este directorio no es un repositorio Git.');
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const spin = this.uiPort.spinner('Cargando remotos...');
|
|
132
|
+
const remotos = await this.gitPort.getRemotes();
|
|
133
|
+
spin.stop();
|
|
134
|
+
const c = this.uiColors;
|
|
135
|
+
this.uiPort.log('');
|
|
136
|
+
this.uiPort.log(c.titulo(' 🌐 Remotos configurados'));
|
|
137
|
+
this.uiPort.separator();
|
|
138
|
+
if (remotos.length === 0) {
|
|
139
|
+
this.uiPort.log(c.tenue(' No hay remotos configurados.'));
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
remotos.forEach((r) => {
|
|
143
|
+
this.uiPort.log(` ${c.info(r.name.padEnd(10))} ${c.tenue(r.refs?.fetch || '')}`);
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
this.uiPort.separator();
|
|
147
|
+
this.uiPort.log('');
|
|
148
|
+
}
|
|
149
|
+
async agregarRemoto() {
|
|
150
|
+
const isRepo = await this.gitPort.isRepository();
|
|
151
|
+
if (!isRepo) {
|
|
152
|
+
this.uiPort.error(' ✗ Este directorio no es un repositorio Git.');
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
const nombre = await this.uiPort.input('Nombre del remoto:', {
|
|
156
|
+
default: 'origin',
|
|
157
|
+
validate: (v) => v.trim().length > 0 || 'Nombre requerido.',
|
|
158
|
+
});
|
|
159
|
+
const url = await this.uiPort.input('URL del remoto:', {
|
|
160
|
+
validate: (v) => v.trim().length > 0 || 'URL requerida.',
|
|
161
|
+
});
|
|
162
|
+
const spinRem = this.uiPort.spinner('Agregando remoto...');
|
|
163
|
+
try {
|
|
164
|
+
await this.gitPort.addRemote(nombre, url);
|
|
165
|
+
spinRem.stop();
|
|
166
|
+
this.uiPort.success(`\n ✓ Remoto "${nombre}" agregado → ${url}\n`);
|
|
167
|
+
}
|
|
168
|
+
catch (err) {
|
|
169
|
+
spinRem.stop();
|
|
170
|
+
const msg = err.message;
|
|
171
|
+
this.uiPort.error(`\n ✗ Error:\n ${msg}\n`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
async inicializarYSubirAGitHub() {
|
|
175
|
+
// Primero, verificar que el usuario esté autenticado
|
|
176
|
+
const isAuth = await this.gitHubService.isAuthenticated();
|
|
177
|
+
if (!isAuth) {
|
|
178
|
+
this.uiPort.error(' ✗ No estás autenticado con GitHub.');
|
|
179
|
+
this.uiPort.info(' Ve al menú "🌐 Gestión GitHub" primero para iniciar sesión.');
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
const c = this.uiColors;
|
|
183
|
+
this.uiPort.log('');
|
|
184
|
+
this.uiPort.log(c.titulo(' 🚀 Crear repo y subir a GitHub'));
|
|
185
|
+
this.uiPort.separator();
|
|
186
|
+
// Paso 1: Obtener datos del repositorio
|
|
187
|
+
const nombreCarpeta = await this.uiPort.input('Nombre de la carpeta y del repositorio:', {
|
|
188
|
+
validate: (v) => v.trim().length > 0 || 'El nombre no puede estar vacío.',
|
|
189
|
+
});
|
|
190
|
+
const descripcion = await this.uiPort.input('Descripción del repositorio (opcional):', {
|
|
191
|
+
default: '',
|
|
192
|
+
});
|
|
193
|
+
const visibilidad = await this.uiPort.select('Visibilidad:', [
|
|
194
|
+
{ name: 'Público', value: 'public' },
|
|
195
|
+
{ name: 'Privado', value: 'private' },
|
|
196
|
+
]);
|
|
197
|
+
const autoInit = await this.uiPort.confirm('¿Agregar README.md inicial?', true);
|
|
198
|
+
const gitignore = autoInit ? await this.uiPort.input('Plantilla .gitignore (ej: Node, dejar vacío para ninguna):', { default: '' }) : undefined;
|
|
199
|
+
const licencia = autoInit ? await this.uiPort.input('Licencia (ej: mit, dejar vacío para ninguna):', { default: '' }) : undefined;
|
|
200
|
+
const ramaInicial = await this.uiPort.input('Nombre de la rama inicial:', {
|
|
201
|
+
default: 'main',
|
|
202
|
+
});
|
|
203
|
+
// Calcular destino
|
|
204
|
+
const destino = path.join(process.cwd(), nombreCarpeta.trim());
|
|
205
|
+
this.uiPort.separator();
|
|
206
|
+
this.uiPort.log(c.titulo(' Resumen:'));
|
|
207
|
+
this.uiPort.log(c.info(` Carpeta: ${destino}`));
|
|
208
|
+
this.uiPort.log(c.info(` Visibilidad: ${visibilidad}`));
|
|
209
|
+
if (descripcion.trim())
|
|
210
|
+
this.uiPort.log(c.tenue(` Descripción: ${descripcion}`));
|
|
211
|
+
this.uiPort.separator();
|
|
212
|
+
const confirmar = await this.uiPort.confirm('¿Confirmar y continuar?', true);
|
|
213
|
+
if (!confirmar) {
|
|
214
|
+
this.uiPort.warning(' Operación cancelada.');
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
// Paso 2: Crear repo en GitHub
|
|
218
|
+
const spinGH = this.uiPort.spinner('Creando repositorio en GitHub...');
|
|
219
|
+
let repoCreado;
|
|
220
|
+
try {
|
|
221
|
+
repoCreado = await this.gitHubService.createRepo({
|
|
222
|
+
name: nombreCarpeta.trim(),
|
|
223
|
+
description: descripcion.trim() || undefined,
|
|
224
|
+
visibility: visibilidad,
|
|
225
|
+
autoInit: autoInit,
|
|
226
|
+
gitignoreTemplate: gitignore?.trim(),
|
|
227
|
+
licenseTemplate: licencia?.trim(),
|
|
228
|
+
});
|
|
229
|
+
spinGH.stop();
|
|
230
|
+
this.uiPort.success(c.exito(` ✓ Repo creado en GitHub: ${repoCreado.htmlUrl}`));
|
|
231
|
+
}
|
|
232
|
+
catch (err) {
|
|
233
|
+
spinGH.stop();
|
|
234
|
+
this.uiPort.error(` ✗ Error al crear repo en GitHub: ${err.message}`);
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
// Paso 3: Clonar o inicializar localmente y subir
|
|
238
|
+
const spinLocal = this.uiPort.spinner('Configurando repositorio local...');
|
|
239
|
+
try {
|
|
240
|
+
const git = simpleGit();
|
|
241
|
+
// Clonar el repo que acabamos de crear
|
|
242
|
+
await git.clone(repoCreado.cloneUrl, nombreCarpeta.trim());
|
|
243
|
+
// Ir a la carpeta
|
|
244
|
+
const gitRepo = simpleGit(destino);
|
|
245
|
+
// Verificar que existan archivos (si usamos autoInit)
|
|
246
|
+
if (autoInit) {
|
|
247
|
+
// Ya tiene contenido, solo confirmar
|
|
248
|
+
spinLocal.stop();
|
|
249
|
+
this.uiPort.success(c.exito(` ✓ Listo! Repositorio en: ${destino}`));
|
|
250
|
+
this.uiPort.log(c.tenue(` URL HTTPS: ${repoCreado.cloneUrl}`));
|
|
251
|
+
this.uiPort.log(c.tenue(` URL SSH: ${repoCreado.sshUrl}`));
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
// Si no usamos autoInit, agregar algún contenido inicial
|
|
255
|
+
spinLocal.stop();
|
|
256
|
+
this.uiPort.info(' Repo creado sin contenido inicial. Puedes agregar archivos y hacer push manualmente.');
|
|
257
|
+
this.uiPort.log(c.tenue(` URL HTTPS: ${repoCreado.cloneUrl}`));
|
|
258
|
+
this.uiPort.log(c.tenue(` URL SSH: ${repoCreado.sshUrl}`));
|
|
259
|
+
}
|
|
260
|
+
this.uiPort.log('');
|
|
261
|
+
}
|
|
262
|
+
catch (err) {
|
|
263
|
+
spinLocal.stop();
|
|
264
|
+
this.uiPort.error(` ✗ Error al configurar localmente: ${err.message}`);
|
|
265
|
+
this.uiPort.warning(' El repositorio sigue existiendo en GitHub.');
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { BaseCommand } from './BaseCommand.js';
|
|
2
|
+
export class StashCommand extends BaseCommand {
|
|
3
|
+
async execute() {
|
|
4
|
+
try {
|
|
5
|
+
const isRepo = await this.gitPort.isRepository();
|
|
6
|
+
if (!isRepo) {
|
|
7
|
+
this.uiPort.error(' ✗ Este directorio no es un repositorio Git.');
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
const accion = await this.uiPort.select('Stash — ¿qué deseas hacer?', [
|
|
11
|
+
{ name: 'Guardar cambios en stash', value: 'guardar' },
|
|
12
|
+
{ name: 'Ver lista de stashes', value: 'listar' },
|
|
13
|
+
{ name: 'Aplicar stash (mantener en lista)', value: 'aplicar' },
|
|
14
|
+
{ name: 'Restaurar y eliminar stash (pop)', value: 'pop' },
|
|
15
|
+
{ name: 'Eliminar un stash', value: 'eliminar' },
|
|
16
|
+
{ name: 'Limpiar todos los stashes', value: 'limpiar' },
|
|
17
|
+
{ name: '← Volver al menú principal', value: 'volver' },
|
|
18
|
+
]);
|
|
19
|
+
switch (accion) {
|
|
20
|
+
case 'guardar':
|
|
21
|
+
await this.guardarStash();
|
|
22
|
+
break;
|
|
23
|
+
case 'listar':
|
|
24
|
+
await this.listarStashes();
|
|
25
|
+
break;
|
|
26
|
+
case 'aplicar':
|
|
27
|
+
await this.operarStash('apply');
|
|
28
|
+
break;
|
|
29
|
+
case 'pop':
|
|
30
|
+
await this.operarStash('pop');
|
|
31
|
+
break;
|
|
32
|
+
case 'eliminar':
|
|
33
|
+
await this.eliminarStash();
|
|
34
|
+
break;
|
|
35
|
+
case 'limpiar':
|
|
36
|
+
await this.limpiarStashes();
|
|
37
|
+
break;
|
|
38
|
+
case 'volver':
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
this.uiPort.error(` ✗ Error: ${err.message}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
async guardarStash() {
|
|
47
|
+
const spin = this.uiPort.spinner('Verificando cambios...');
|
|
48
|
+
const status = await this.gitPort.getStatus();
|
|
49
|
+
spin.stop();
|
|
50
|
+
const hayModificados = status.modified.length + status.staged.length + status.not_added.length;
|
|
51
|
+
if (hayModificados === 0) {
|
|
52
|
+
this.uiPort.warning('\n No hay cambios para guardar en stash.\n');
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const mensaje = await this.uiPort.input('Mensaje del stash (opcional):');
|
|
56
|
+
const incluirNoSeguidos = await this.uiPort.confirm('¿Incluir archivos sin seguimiento (untracked)?', false);
|
|
57
|
+
const spinStash = this.uiPort.spinner('Guardando en stash...');
|
|
58
|
+
await this.gitPort.stashPush(mensaje.trim() || undefined, incluirNoSeguidos);
|
|
59
|
+
spinStash.stop();
|
|
60
|
+
this.uiPort.success('\n ✓ Cambios guardados en stash. Árbol limpio.\n');
|
|
61
|
+
}
|
|
62
|
+
async listarStashes() {
|
|
63
|
+
const spin = this.uiPort.spinner('Cargando stashes...');
|
|
64
|
+
const lista = await this.gitPort.stashList();
|
|
65
|
+
spin.stop();
|
|
66
|
+
const c = this.uiColors;
|
|
67
|
+
this.uiPort.log('');
|
|
68
|
+
this.uiPort.log(c.titulo(' 📦 Stashes guardados'));
|
|
69
|
+
this.uiPort.separator();
|
|
70
|
+
if (lista.all.length === 0) {
|
|
71
|
+
this.uiPort.log(c.tenue(' No hay stashes guardados.'));
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
lista.all.forEach((s, i) => {
|
|
75
|
+
const fecha = new Date(s.date).toLocaleDateString('es-CL', {
|
|
76
|
+
day: '2-digit',
|
|
77
|
+
month: 'short',
|
|
78
|
+
year: 'numeric',
|
|
79
|
+
});
|
|
80
|
+
this.uiPort.log(` ${c.hash(`stash@{${i}}`)} ${c.tenue(fecha)} ${c.titulo(s.message)}`);
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
this.uiPort.separator();
|
|
84
|
+
this.uiPort.log('');
|
|
85
|
+
}
|
|
86
|
+
async operarStash(modo) {
|
|
87
|
+
const spin = this.uiPort.spinner('Cargando stashes...');
|
|
88
|
+
const lista = await this.gitPort.stashList();
|
|
89
|
+
spin.stop();
|
|
90
|
+
if (lista.all.length === 0) {
|
|
91
|
+
this.uiPort.warning('\n No hay stashes disponibles.\n');
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const opciones = lista.all.map((s, i) => ({
|
|
95
|
+
name: `stash@{${i}} ${s.message}`,
|
|
96
|
+
value: `stash@{${i}}`,
|
|
97
|
+
}));
|
|
98
|
+
const seleccionado = await this.uiPort.select(modo === 'pop' ? '¿Qué stash restaurar y eliminar (pop)?' : '¿Qué stash aplicar?', opciones);
|
|
99
|
+
const label = modo === 'pop' ? 'Restaurando y eliminando' : 'Aplicando';
|
|
100
|
+
const spinOp = this.uiPort.spinner(`${label} ${seleccionado}...`);
|
|
101
|
+
if (modo === 'pop') {
|
|
102
|
+
await this.gitPort.stashPop(seleccionado);
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
await this.gitPort.stashApply(seleccionado);
|
|
106
|
+
}
|
|
107
|
+
spinOp.stop();
|
|
108
|
+
this.uiPort.success(`\n ✓ ${seleccionado} ${modo === 'pop' ? 'restaurado y eliminado' : 'aplicado'}.\n`);
|
|
109
|
+
}
|
|
110
|
+
async eliminarStash() {
|
|
111
|
+
const spin = this.uiPort.spinner('Cargando stashes...');
|
|
112
|
+
const lista = await this.gitPort.stashList();
|
|
113
|
+
spin.stop();
|
|
114
|
+
if (lista.all.length === 0) {
|
|
115
|
+
this.uiPort.warning('\n No hay stashes disponibles.\n');
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
const opciones = lista.all.map((s, i) => ({
|
|
119
|
+
name: `stash@{${i}} ${s.message}`,
|
|
120
|
+
value: `stash@{${i}}`,
|
|
121
|
+
}));
|
|
122
|
+
const seleccionado = await this.uiPort.select('¿Qué stash deseas eliminar?', opciones);
|
|
123
|
+
const confirmar = await this.uiPort.confirm(`¿Eliminar ${seleccionado}?`, false);
|
|
124
|
+
if (!confirmar)
|
|
125
|
+
return;
|
|
126
|
+
const spinDel = this.uiPort.spinner('Eliminando...');
|
|
127
|
+
await this.gitPort.stashDrop(seleccionado);
|
|
128
|
+
spinDel.stop();
|
|
129
|
+
this.uiPort.success(`\n ✓ ${seleccionado} eliminado.\n`);
|
|
130
|
+
}
|
|
131
|
+
async limpiarStashes() {
|
|
132
|
+
const confirmar = await this.uiPort.confirm(this.uiColors.error('¿Eliminar TODOS los stashes? Esta acción es irreversible.'), false);
|
|
133
|
+
if (!confirmar)
|
|
134
|
+
return;
|
|
135
|
+
const spin = this.uiPort.spinner('Limpiando stashes...');
|
|
136
|
+
await this.gitPort.stashClear();
|
|
137
|
+
spin.stop();
|
|
138
|
+
this.uiPort.success('\n ✓ Todos los stashes eliminados.\n');
|
|
139
|
+
}
|
|
140
|
+
}
|