@soccagency/sh 0.2.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 +242 -0
- package/bin/socc.js +2 -0
- package/package.json +46 -0
- package/src/index.js +689 -0
package/README.md
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
# Socc CLI
|
|
2
|
+
|
|
3
|
+
CLI para subir proyectos a Socc y obtener preview links instantáneos.
|
|
4
|
+
|
|
5
|
+
## 🛠️ Stack
|
|
6
|
+
|
|
7
|
+
- **Runtime:** Node.js 20+
|
|
8
|
+
- **Package:** npm
|
|
9
|
+
- **Comprimiendo:** archiver
|
|
10
|
+
- **HTTP:** undici (fetch nativo)
|
|
11
|
+
|
|
12
|
+
## 📋 Requisitos
|
|
13
|
+
|
|
14
|
+
- Node.js 20+
|
|
15
|
+
- npm o yarn
|
|
16
|
+
- Token de Socc (obtenido desde sh.socc.ink)
|
|
17
|
+
|
|
18
|
+
## 🚀 Instalación
|
|
19
|
+
|
|
20
|
+
### Desde npm (cuando esté publicado)
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install -g @socc/sh
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Desde código fuente
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
# Clonar o navegar al directorio del CLI
|
|
30
|
+
cd /home/clawdbot/projects/socc/cli
|
|
31
|
+
|
|
32
|
+
# Instalar dependencias
|
|
33
|
+
npm install
|
|
34
|
+
|
|
35
|
+
# Instalar globalmente (desarrollo)
|
|
36
|
+
npm install -g .
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## 🔧 Comandos
|
|
40
|
+
|
|
41
|
+
### `socc login <token>`
|
|
42
|
+
|
|
43
|
+
Inicia sesión con tu API token.
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
socc login socc_cw5MIU0haehbgAJma3vw4pXUqENHu8vj
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
**Obtener tu token:**
|
|
50
|
+
|
|
51
|
+
1. Andá a https://sh.socc.ink
|
|
52
|
+
2. Logueate con GitHub
|
|
53
|
+
3. Click en tu avatar → Settings
|
|
54
|
+
4. Click en "Generar API Token"
|
|
55
|
+
5. Copiá el token
|
|
56
|
+
|
|
57
|
+
### `socc link [--ttl <horas>] [--framework <name>]`
|
|
58
|
+
|
|
59
|
+
Sube tu proyecto actual y crea un preview link.
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
cd mi-proyecto
|
|
63
|
+
socc link
|
|
64
|
+
socc link --ttl 1
|
|
65
|
+
socc link --framework vite
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**Output:**
|
|
69
|
+
```
|
|
70
|
+
🚀 Socc - Deploy instantáneo
|
|
71
|
+
|
|
72
|
+
🔐 Verificando tu cuenta...
|
|
73
|
+
✅ Autenticado como: tu@email.com
|
|
74
|
+
📊 Plan: FREE
|
|
75
|
+
📈 Deployments activos: 0/1
|
|
76
|
+
📅 Deployments hoy: 0/5
|
|
77
|
+
📦 Framework usado: static
|
|
78
|
+
⏰ TTL solicitado: 1 hora(s)
|
|
79
|
+
📦 Comprimiendo archivos...
|
|
80
|
+
✅ Archivos comprimidos
|
|
81
|
+
📤 Subiendo a Socc...
|
|
82
|
+
✅ Uploaded emqg9i7y/deploy.zip to R2 (0.00 MB)
|
|
83
|
+
✅ Extracted 3 files to R2
|
|
84
|
+
|
|
85
|
+
✅ ¡Deploy exitoso!
|
|
86
|
+
|
|
87
|
+
🚀 Tu preview está en: https://emqg9i7y.socc.ink
|
|
88
|
+
⏰ Expira en: 6 horas
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### `socc list`
|
|
92
|
+
|
|
93
|
+
Lista tus deployments activos.
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
socc list
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
**Output:**
|
|
100
|
+
```
|
|
101
|
+
📋 Obteniendo tus deployments...
|
|
102
|
+
|
|
103
|
+
📦 Tus deployments (2):
|
|
104
|
+
|
|
105
|
+
1. 🟢 emqg9i7y
|
|
106
|
+
URL: https://emqg9i7y.socc.ink
|
|
107
|
+
Framework: static
|
|
108
|
+
Tamaño: 0.01 MB
|
|
109
|
+
Expira en: 5h
|
|
110
|
+
|
|
111
|
+
2. 🟢 xQ39GogI
|
|
112
|
+
URL: https://xQ39GogI.socc.ink
|
|
113
|
+
Framework: react
|
|
114
|
+
Tamaño: 1.23 MB
|
|
115
|
+
Expira en: 23h
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### `socc delete <id>`
|
|
119
|
+
|
|
120
|
+
Elimina un deployment.
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
socc delete emqg9i7y
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
**Output:**
|
|
127
|
+
```
|
|
128
|
+
🗑️ Borrando deployment emqg9i7y...
|
|
129
|
+
✅ Deployment emqg9i7y eliminado.
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### `socc whoami`
|
|
133
|
+
|
|
134
|
+
Muestra información de tu cuenta.
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
socc whoami
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
**Output:**
|
|
141
|
+
```
|
|
142
|
+
👤 Usuario: tu@email.com
|
|
143
|
+
📊 Plan: FREE
|
|
144
|
+
📈 Deployments activos: 0/1
|
|
145
|
+
📅 Deployments hoy: 2/5
|
|
146
|
+
📦 Total links: 15
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## 📦 Frameworks Detectados
|
|
150
|
+
|
|
151
|
+
La CLI detecta automáticamente el framework y configura el build.
|
|
152
|
+
También detecta package manager (`npm`, `yarn`, `pnpm`, `bun`) por lockfile:
|
|
153
|
+
|
|
154
|
+
| Framework | Detecta | Build | Output |
|
|
155
|
+
|-----------|---------|-------|--------|
|
|
156
|
+
| **Vite** | `vite.config.js` | `npm run build` | `dist/` |
|
|
157
|
+
| **React (CRA)** | `package.json` + react-scripts | `npm run build` | `build/` |
|
|
158
|
+
| **Next.js** | `next.config.js` | `npm run build` | `out/` (static) |
|
|
159
|
+
| **Angular** | `angular.json` | `ng build` | `dist/` |
|
|
160
|
+
| **Astro** | `astro.config.*` o dependencia `astro` | `build` | `dist/` |
|
|
161
|
+
| **Static** | Por defecto | - | `.` |
|
|
162
|
+
| **Custom build** | `scripts.build` sin framework conocido | `build` | `dist/`, `build/` u `out/` |
|
|
163
|
+
|
|
164
|
+
Frameworks válidos para `--framework`:
|
|
165
|
+
|
|
166
|
+
`auto`, `vite`, `react`, `next`, `angular`, `nuxt`, `static`
|
|
167
|
+
|
|
168
|
+
> Para Next.js, Socc exige export estático (`out/`).
|
|
169
|
+
> Configurá `output: "export"` en `next.config.js`.
|
|
170
|
+
|
|
171
|
+
## 🔍 Cómo funciona
|
|
172
|
+
|
|
173
|
+
1. **Detecta el framework** buscando archivos de configuración
|
|
174
|
+
2. **Ejecuta el build** si es necesario
|
|
175
|
+
3. **Comprime** el output en un ZIP
|
|
176
|
+
4. **Envía** el ZIP a la API (`POST /api/link`)
|
|
177
|
+
5. **API** extrae y sube archivos a R2
|
|
178
|
+
6. **Recibe** la URL del preview
|
|
179
|
+
|
|
180
|
+
## 🐛 Troubleshooting
|
|
181
|
+
|
|
182
|
+
### Error: "No estás autenticado"
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
socc login <tu-token>
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Error: "Token inválido o expirado"
|
|
189
|
+
|
|
190
|
+
- El token puede haber sido revocado
|
|
191
|
+
- Generá uno nuevo en Settings
|
|
192
|
+
|
|
193
|
+
### Error: "Llegaste al límite diario"
|
|
194
|
+
|
|
195
|
+
- Tu plan tiene un límite de links por día
|
|
196
|
+
- Esperá al próximo día o actualizá tu plan
|
|
197
|
+
|
|
198
|
+
### Error: "Build fallido"
|
|
199
|
+
|
|
200
|
+
- Verificá que el proyecto compila localmente
|
|
201
|
+
- Revisá que tenés las dependencias instaladas
|
|
202
|
+
|
|
203
|
+
### Error: "File too large"
|
|
204
|
+
|
|
205
|
+
- Tu plan tiene un límite de upload
|
|
206
|
+
- Free: 10 MB, Starter: 50 MB, Pro: 100 MB
|
|
207
|
+
|
|
208
|
+
## 📝 Configuración
|
|
209
|
+
|
|
210
|
+
El CLI guarda tu token en:
|
|
211
|
+
|
|
212
|
+
```
|
|
213
|
+
~/.socc/config.json
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Contenido:
|
|
217
|
+
```json
|
|
218
|
+
{
|
|
219
|
+
"token": "socc_cw5MIU0haehbgAJma3vw4pXUqENHu8vj"
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
También podés cambiar la API con variable de entorno:
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
SOCC_API_URL=http://localhost:3000 socc whoami
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
## 🚀 Publicar en npm
|
|
230
|
+
|
|
231
|
+
```bash
|
|
232
|
+
# Publicar versión actual (ej: 0.2.0)
|
|
233
|
+
npm whoami
|
|
234
|
+
npm publish
|
|
235
|
+
|
|
236
|
+
# Verificar paquete antes de publicar
|
|
237
|
+
npm run pack:check
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
[Volver al README principal](../README.md)
|
package/bin/socc.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@soccagency/sh",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Instant preview links for your projects",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"socc": "bin/socc.js"
|
|
8
|
+
},
|
|
9
|
+
"type": "module",
|
|
10
|
+
"scripts": {
|
|
11
|
+
"pack:check": "npm pack --dry-run"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"bin",
|
|
15
|
+
"src",
|
|
16
|
+
"README.md"
|
|
17
|
+
],
|
|
18
|
+
"keywords": [
|
|
19
|
+
"deploy",
|
|
20
|
+
"preview",
|
|
21
|
+
"hosting",
|
|
22
|
+
"cli",
|
|
23
|
+
"tunnel"
|
|
24
|
+
],
|
|
25
|
+
"author": "Socc Team",
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "git+https://github.com/GuilleG25/socc.git",
|
|
30
|
+
"directory": "cli"
|
|
31
|
+
},
|
|
32
|
+
"homepage": "https://sh.socc.ink",
|
|
33
|
+
"bugs": {
|
|
34
|
+
"url": "https://github.com/GuilleG25/socc/issues"
|
|
35
|
+
},
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": ">=20"
|
|
38
|
+
},
|
|
39
|
+
"publishConfig": {
|
|
40
|
+
"access": "public"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"archiver": "^7.0.1",
|
|
44
|
+
"undici": "^7.22.0"
|
|
45
|
+
}
|
|
46
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,689 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { execSync } from 'child_process';
|
|
4
|
+
import { readFileSync, writeFileSync, existsSync, unlinkSync, mkdirSync } from 'fs';
|
|
5
|
+
import { join } from 'path';
|
|
6
|
+
import { createWriteStream } from 'fs';
|
|
7
|
+
import archiver from 'archiver';
|
|
8
|
+
import { fetch } from 'undici';
|
|
9
|
+
|
|
10
|
+
const API_URL = process.env.SOCC_API_URL || 'https://api.socc.ink';
|
|
11
|
+
|
|
12
|
+
const ALLOWED_FRAMEWORKS = ['auto', 'vite', 'react', 'next', 'angular', 'nuxt', 'astro', 'static'];
|
|
13
|
+
|
|
14
|
+
const FRAMEWORK_CONFIGS = {
|
|
15
|
+
vite: { name: 'vite', buildScript: 'build', distCandidates: ['dist'] },
|
|
16
|
+
react: { name: 'react', buildScript: 'build', distCandidates: ['build'] },
|
|
17
|
+
next: { name: 'next', buildScript: 'build', distCandidates: ['out'] },
|
|
18
|
+
angular: { name: 'angular', buildScript: 'build', distCandidates: ['dist'] },
|
|
19
|
+
nuxt: { name: 'nuxt', buildScript: 'generate', distCandidates: ['dist'] },
|
|
20
|
+
astro: { name: 'astro', buildScript: 'build', distCandidates: ['dist'] },
|
|
21
|
+
static: { name: 'static', buildScript: null, distCandidates: ['dist', '.'] }
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// Colores
|
|
25
|
+
const colors = {
|
|
26
|
+
reset: '\x1b[0m',
|
|
27
|
+
red: '\x1b[31m',
|
|
28
|
+
green: '\x1b[32m',
|
|
29
|
+
yellow: '\x1b[33m',
|
|
30
|
+
blue: '\x1b[34m',
|
|
31
|
+
cyan: '\x1b[36m',
|
|
32
|
+
gray: '\x1b[90m',
|
|
33
|
+
white: '\x1b[37m'
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
function log(msg, color = 'reset') {
|
|
37
|
+
console.log(`${colors[color]}${msg}${colors.reset}`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ============= COMANDOS =============
|
|
41
|
+
|
|
42
|
+
function showHelp() {
|
|
43
|
+
log(`
|
|
44
|
+
🚀 Socc CLI - Deploy instantáneo
|
|
45
|
+
|
|
46
|
+
Uso:
|
|
47
|
+
socc Mostrar esta ayuda
|
|
48
|
+
socc link [--ttl <horas>] [--framework <${ALLOWED_FRAMEWORKS.join('|')}>]
|
|
49
|
+
socc list Listar tus deployments
|
|
50
|
+
socc delete <id> Borrar un deployment
|
|
51
|
+
socc login <token> Iniciar sesión con API token
|
|
52
|
+
socc whoami Ver info de tu cuenta
|
|
53
|
+
socc help Mostrar esta ayuda
|
|
54
|
+
|
|
55
|
+
Flags de socc link:
|
|
56
|
+
--ttl <horas> TTL en horas (entero positivo, por defecto 6)
|
|
57
|
+
--framework <name> Fuerza framework: auto|vite|react|next|angular|nuxt|astro|static
|
|
58
|
+
|
|
59
|
+
Variables de entorno:
|
|
60
|
+
SOCC_API_URL API base URL (default: https://api.socc.ink)
|
|
61
|
+
|
|
62
|
+
Ejemplos:
|
|
63
|
+
$ socc login socc_abcdef123...
|
|
64
|
+
$ socc link --ttl 1
|
|
65
|
+
$ socc link --framework vite
|
|
66
|
+
$ SOCC_API_URL=http://localhost:3000 socc whoami
|
|
67
|
+
|
|
68
|
+
Planes:
|
|
69
|
+
Free - 1 deployment activo, 5/día, 10MB max
|
|
70
|
+
Starter - 5 deployments activos, 50/día, 50MB max
|
|
71
|
+
Pro - 20 deployments activos, 200/día, 100MB max
|
|
72
|
+
Enterprise - Ilimitado
|
|
73
|
+
`, 'cyan');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function cmdLogin(args) {
|
|
77
|
+
const token = args[0];
|
|
78
|
+
|
|
79
|
+
if (!token) {
|
|
80
|
+
log('\n🔐 Socc Login\n', 'cyan');
|
|
81
|
+
log('Uso: socc login <tu-api-token>\n', 'white');
|
|
82
|
+
log('📋 Cómo obtener tu API token:', 'yellow');
|
|
83
|
+
log('1. Andá a https://sh.socc.ink y logueate con GitHub', 'yellow');
|
|
84
|
+
log('2. Hacé click en tu avatar → Settings', 'yellow');
|
|
85
|
+
log('3. Click en "Generar API Token"', 'yellow');
|
|
86
|
+
log('4. Copiá el token y ejecutá: socc login socc_xxxxx\n', 'yellow');
|
|
87
|
+
process.exit(0);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
log('🔐 Verificando token...', 'yellow');
|
|
91
|
+
const userInfo = await getUserInfo(token);
|
|
92
|
+
|
|
93
|
+
if (!userInfo.success) {
|
|
94
|
+
log(`❌ Token inválido: ${userInfo.error}`, 'red');
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Guardar token
|
|
99
|
+
const configDir = join(process.env.HOME, '.socc');
|
|
100
|
+
const configPath = join(configDir, 'config.json');
|
|
101
|
+
|
|
102
|
+
if (!existsSync(configDir)) {
|
|
103
|
+
mkdirSync(configDir, { recursive: true });
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
writeFileSync(configPath, JSON.stringify({ token }, null, 2));
|
|
107
|
+
|
|
108
|
+
log('✅ ¡Login exitoso!', 'green');
|
|
109
|
+
log(`\n👤 Autenticado como: ${userInfo.user.email}`, 'white');
|
|
110
|
+
log(`📊 Plan: ${userInfo.user.tier.toUpperCase()}`, 'blue');
|
|
111
|
+
log('\n🚀 Ahora podés usar: socc link\n', 'cyan');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async function cmdWhoami() {
|
|
115
|
+
const token = getToken();
|
|
116
|
+
|
|
117
|
+
if (!token) {
|
|
118
|
+
log('❌ No estás autenticado. Corré `socc login` primero.', 'red');
|
|
119
|
+
process.exit(1);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const userInfo = await getUserInfo(token);
|
|
123
|
+
|
|
124
|
+
if (!userInfo.success) {
|
|
125
|
+
log(`❌ Error: ${userInfo.error}`, 'red');
|
|
126
|
+
process.exit(1);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const { user, usage } = userInfo;
|
|
130
|
+
|
|
131
|
+
log('\n👤 Tu cuenta Socc\n', 'cyan');
|
|
132
|
+
log(`Email: ${user.email}`, 'white');
|
|
133
|
+
log(`Plan: ${user.tier.toUpperCase()}`, 'blue');
|
|
134
|
+
log(`Estado: ${user.subscriptionStatus || 'active'}`, 'green');
|
|
135
|
+
log('\n📊 Uso:', 'yellow');
|
|
136
|
+
log(` Deployments activos: ${usage.activeDeployments}/${usage.maxActiveDeployments}`, 'white');
|
|
137
|
+
log(` Deployments hoy: ${usage.linksToday}/${usage.linksLimit}`, 'white');
|
|
138
|
+
log(` Max upload: ${usage.maxUpload}`, 'white');
|
|
139
|
+
log(` Max TTL: ${usage.maxTtl}`, 'white');
|
|
140
|
+
log(`\n📈 Total deployments: ${usage.totalLinks}\n`, 'gray');
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async function cmdList() {
|
|
144
|
+
const token = getToken();
|
|
145
|
+
|
|
146
|
+
if (!token) {
|
|
147
|
+
log('❌ No estás autenticado. Corré `socc login` primero.', 'red');
|
|
148
|
+
process.exit(1);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
log('📋 Obteniendo tus deployments...', 'yellow');
|
|
152
|
+
|
|
153
|
+
try {
|
|
154
|
+
const response = await fetch(`${API_URL}/api/list`, {
|
|
155
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
const data = await response.json();
|
|
159
|
+
|
|
160
|
+
if (!data.success) {
|
|
161
|
+
log(`❌ Error: ${data.error}`, 'red');
|
|
162
|
+
process.exit(1);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (data.deployments.length === 0) {
|
|
166
|
+
log('\n📭 No tenés deployments activos.', 'gray');
|
|
167
|
+
log('Creá tu primer deploy con: socc link\n', 'cyan');
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
log('\n📦 Tus deployments:', 'cyan');
|
|
172
|
+
|
|
173
|
+
data.deployments.forEach((d, i) => {
|
|
174
|
+
const expires = new Date(d.expiresAt);
|
|
175
|
+
const now = new Date();
|
|
176
|
+
const hoursLeft = Math.round((expires - now) / (1000 * 60 * 60));
|
|
177
|
+
const status = hoursLeft > 0 ? '🟢' : '🔴';
|
|
178
|
+
const id = d.id || d.projectId || 'unknown';
|
|
179
|
+
const subdomain = d.subdomain || id;
|
|
180
|
+
const framework = d.framework || 'static';
|
|
181
|
+
const sizeMB = d.fileSize ? (d.fileSize / 1024 / 1024).toFixed(2) : '0.00';
|
|
182
|
+
|
|
183
|
+
console.log(` ${i + 1}. ${status} ${id}`);
|
|
184
|
+
console.log(` URL: https://${subdomain}.socc.ink`);
|
|
185
|
+
console.log(` Framework: ${framework}`);
|
|
186
|
+
console.log(` Tamaño: ${sizeMB} MB`);
|
|
187
|
+
console.log(` Expira en: ${hoursLeft > 0 ? `${hoursLeft}h` : 'EXPIRADO'}`);
|
|
188
|
+
console.log('');
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
} catch (error) {
|
|
192
|
+
log(`❌ Error: ${error.message}`, 'red');
|
|
193
|
+
process.exit(1);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
async function cmdDelete(args) {
|
|
198
|
+
const projectId = args[0];
|
|
199
|
+
|
|
200
|
+
if (!projectId) {
|
|
201
|
+
log('\n❌ Debes especificar un ID de deployment', 'red');
|
|
202
|
+
log('\nUso: socc delete <project-id>\n', 'yellow');
|
|
203
|
+
log('Obtené el ID con: socc list\n', 'cyan');
|
|
204
|
+
process.exit(1);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const token = getToken();
|
|
208
|
+
|
|
209
|
+
if (!token) {
|
|
210
|
+
log('❌ No estás autenticado. Corré `socc login` primero.', 'red');
|
|
211
|
+
process.exit(1);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
log(`🗑️ Borrando deployment ${projectId}...`, 'yellow');
|
|
215
|
+
|
|
216
|
+
try {
|
|
217
|
+
const response = await fetch(`${API_URL}/api/delete/${projectId}`, {
|
|
218
|
+
method: 'DELETE',
|
|
219
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
const data = await response.json();
|
|
223
|
+
|
|
224
|
+
if (!data.success) {
|
|
225
|
+
log(`❌ Error: ${data.error}`, 'red');
|
|
226
|
+
process.exit(1);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
log(`✅ Deployment ${projectId} eliminado.\n`, 'green');
|
|
230
|
+
|
|
231
|
+
} catch (error) {
|
|
232
|
+
log(`❌ Error: ${error.message}`, 'red');
|
|
233
|
+
process.exit(1);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
async function cmdLink(args) {
|
|
238
|
+
const parsedOptions = parseLinkOptions(args);
|
|
239
|
+
|
|
240
|
+
if (!parsedOptions.ok) {
|
|
241
|
+
log(`❌ ${parsedOptions.error}`, 'red');
|
|
242
|
+
log(`\nUso: socc link [--ttl <horas>] [--framework <${ALLOWED_FRAMEWORKS.join('|')}>]\n`, 'yellow');
|
|
243
|
+
process.exit(1);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const options = parsedOptions.options;
|
|
247
|
+
|
|
248
|
+
log('🚀 Socc - Deploy instantáneo\n', 'cyan');
|
|
249
|
+
|
|
250
|
+
const token = getToken();
|
|
251
|
+
|
|
252
|
+
if (!token) {
|
|
253
|
+
log('❌ No estás autenticado. Corré `socc login` primero.', 'red');
|
|
254
|
+
log('\nPara loguearte: socc login socc_xxxxx\n', 'cyan');
|
|
255
|
+
process.exit(1);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Verificar límites
|
|
259
|
+
log('🔐 Verificando tu cuenta...', 'yellow');
|
|
260
|
+
const userInfo = await getUserInfo(token);
|
|
261
|
+
|
|
262
|
+
if (!userInfo.success) {
|
|
263
|
+
log(`❌ Error: ${userInfo.error}`, 'red');
|
|
264
|
+
process.exit(1);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const { user, usage, canCreateLink } = userInfo;
|
|
268
|
+
|
|
269
|
+
log(`✅ Autenticado como: ${user.email}`, 'green');
|
|
270
|
+
log(`📊 Plan: ${user.tier.toUpperCase()}`, 'blue');
|
|
271
|
+
log(`📈 Deployments activos: ${usage.activeDeployments}/${usage.maxActiveDeployments}`, 'blue');
|
|
272
|
+
log(`📅 Deployments hoy: ${usage.linksToday}/${usage.linksLimit}`, 'blue');
|
|
273
|
+
|
|
274
|
+
if (!canCreateLink) {
|
|
275
|
+
log('\n❌ Llegaste al límite diario de deployments.', 'red');
|
|
276
|
+
log(`Tu plan (${user.tier}) permite ${usage.linksLimit} deployments por día.`, 'yellow');
|
|
277
|
+
log('Volvé mañana o actualizá tu plan.\n', 'yellow');
|
|
278
|
+
process.exit(1);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (usage.activeDeployments >= usage.maxActiveDeployments && usage.maxActiveDeployments !== 'unlimited') {
|
|
282
|
+
log('\n❌ Llegaste al máximo de deployments activos.', 'red');
|
|
283
|
+
log(`Tu plan (${user.tier}) permite ${usage.maxActiveDeployments} deployments activos.`, 'yellow');
|
|
284
|
+
log('Eliminá previews viejos con: socc delete <id>', 'yellow');
|
|
285
|
+
log('O actualizá tu plan.\n', 'yellow');
|
|
286
|
+
process.exit(1);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Detectar framework
|
|
290
|
+
const framework = detectFramework(options.framework);
|
|
291
|
+
const packageManager = detectPackageManager();
|
|
292
|
+
log(`📦 Framework usado: ${framework.name}`, 'blue');
|
|
293
|
+
log(`📦 Package manager detectado: ${packageManager}`, 'blue');
|
|
294
|
+
log(`⏰ TTL solicitado: ${options.ttl} hora(s)`, 'blue');
|
|
295
|
+
|
|
296
|
+
// Build
|
|
297
|
+
if (framework.buildScript) {
|
|
298
|
+
const buildCommand = buildCommandForScript(packageManager, framework.buildScript);
|
|
299
|
+
log(`🔨 Construyendo proyecto (${buildCommand})...`, 'yellow');
|
|
300
|
+
try {
|
|
301
|
+
execSync(buildCommand, { stdio: 'inherit' });
|
|
302
|
+
log('✅ Build completado', 'green');
|
|
303
|
+
} catch {
|
|
304
|
+
log('❌ Build fallido', 'red');
|
|
305
|
+
process.exit(1);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
let distPath;
|
|
310
|
+
try {
|
|
311
|
+
distPath = resolveDistPath(framework);
|
|
312
|
+
} catch (error) {
|
|
313
|
+
log(`❌ ${error.message}`, 'red');
|
|
314
|
+
process.exit(1);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Comprimir
|
|
318
|
+
log('📦 Comprimiendo archivos...', 'yellow');
|
|
319
|
+
const zipPath = await zipDirectory(distPath);
|
|
320
|
+
log('✅ Archivos comprimidos', 'green');
|
|
321
|
+
|
|
322
|
+
// Upload
|
|
323
|
+
log('📤 Subiendo a Socc...', 'yellow');
|
|
324
|
+
try {
|
|
325
|
+
const result = await uploadToApi(zipPath, token, {
|
|
326
|
+
ttl: options.ttl,
|
|
327
|
+
framework: framework.name
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
if (result.success && result.url) {
|
|
331
|
+
log('\n✅ ¡Deploy exitoso!', 'green');
|
|
332
|
+
log(`\n🚀 Tu preview está en: ${result.url}\n`, 'cyan');
|
|
333
|
+
log(`⏰ Expira en: ${result.ttl} horas\n`, 'blue');
|
|
334
|
+
} else {
|
|
335
|
+
log('❌ Error en el deploy', 'red');
|
|
336
|
+
if (result.error) {
|
|
337
|
+
log(result.error, 'red');
|
|
338
|
+
}
|
|
339
|
+
process.exit(1);
|
|
340
|
+
}
|
|
341
|
+
} catch (error) {
|
|
342
|
+
log(`❌ Error: ${error.message}`, 'red');
|
|
343
|
+
process.exit(1);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Limpiar
|
|
347
|
+
try {
|
|
348
|
+
unlinkSync(zipPath);
|
|
349
|
+
} catch {
|
|
350
|
+
// ignore cleanup errors
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// ============= HELPERS =============
|
|
355
|
+
|
|
356
|
+
function getToken() {
|
|
357
|
+
const configPath = join(process.env.HOME, '.socc', 'config.json');
|
|
358
|
+
if (!existsSync(configPath)) {
|
|
359
|
+
return null;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
try {
|
|
363
|
+
const config = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
364
|
+
return config.token;
|
|
365
|
+
} catch {
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
async function getUserInfo(token) {
|
|
371
|
+
try {
|
|
372
|
+
const response = await fetch(`${API_URL}/api/whoami`, {
|
|
373
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
if (response.status === 401) {
|
|
377
|
+
return { success: false, error: 'Token inválido o expirado' };
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const data = await response.json();
|
|
381
|
+
|
|
382
|
+
if (!data || !data.success) {
|
|
383
|
+
return { success: false, error: data?.error || 'Error al verificar usuario' };
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
return {
|
|
387
|
+
success: true,
|
|
388
|
+
user: data.user || {},
|
|
389
|
+
usage: data.usage || {},
|
|
390
|
+
limits: data.limits || {},
|
|
391
|
+
canCreateLink: data.canCreateLink !== false
|
|
392
|
+
};
|
|
393
|
+
} catch (error) {
|
|
394
|
+
return { success: false, error: error.message || 'Error de conexión' };
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function parseLinkOptions(args) {
|
|
399
|
+
let ttl = 6;
|
|
400
|
+
let framework = 'auto';
|
|
401
|
+
|
|
402
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
403
|
+
const arg = args[i];
|
|
404
|
+
|
|
405
|
+
if (arg === '--ttl') {
|
|
406
|
+
const ttlValue = args[i + 1];
|
|
407
|
+
if (!ttlValue) {
|
|
408
|
+
return { ok: false, error: 'Falta valor para --ttl' };
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const parsedTtl = Number.parseInt(ttlValue, 10);
|
|
412
|
+
if (!Number.isInteger(parsedTtl) || parsedTtl <= 0) {
|
|
413
|
+
return { ok: false, error: '--ttl debe ser un entero positivo' };
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
ttl = parsedTtl;
|
|
417
|
+
i += 1;
|
|
418
|
+
continue;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
if (arg.startsWith('--ttl=')) {
|
|
422
|
+
const ttlValue = arg.split('=')[1];
|
|
423
|
+
const parsedTtl = Number.parseInt(ttlValue, 10);
|
|
424
|
+
if (!Number.isInteger(parsedTtl) || parsedTtl <= 0) {
|
|
425
|
+
return { ok: false, error: '--ttl debe ser un entero positivo' };
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
ttl = parsedTtl;
|
|
429
|
+
continue;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
if (arg === '--framework') {
|
|
433
|
+
const frameworkValue = args[i + 1];
|
|
434
|
+
if (!frameworkValue) {
|
|
435
|
+
return { ok: false, error: 'Falta valor para --framework' };
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
if (!ALLOWED_FRAMEWORKS.includes(frameworkValue)) {
|
|
439
|
+
return { ok: false, error: `Framework inválido: ${frameworkValue}` };
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
framework = frameworkValue;
|
|
443
|
+
i += 1;
|
|
444
|
+
continue;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
if (arg.startsWith('--framework=')) {
|
|
448
|
+
const frameworkValue = arg.split('=')[1];
|
|
449
|
+
if (!ALLOWED_FRAMEWORKS.includes(frameworkValue)) {
|
|
450
|
+
return { ok: false, error: `Framework inválido: ${frameworkValue}` };
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
framework = frameworkValue;
|
|
454
|
+
continue;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
return { ok: false, error: `Flag no soportada: ${arg}` };
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
return {
|
|
461
|
+
ok: true,
|
|
462
|
+
options: {
|
|
463
|
+
ttl,
|
|
464
|
+
framework
|
|
465
|
+
}
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
function detectFramework(preferredFramework = 'auto') {
|
|
470
|
+
if (preferredFramework !== 'auto') {
|
|
471
|
+
return { ...FRAMEWORK_CONFIGS[preferredFramework] };
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
const pkgPath = join(process.cwd(), 'package.json');
|
|
475
|
+
|
|
476
|
+
if (!existsSync(pkgPath)) {
|
|
477
|
+
if (existsSync(join(process.cwd(), 'index.html'))) {
|
|
478
|
+
return { ...FRAMEWORK_CONFIGS.static, distCandidates: ['.'] };
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
log('❌ No se encontró package.json. Si es un sitio estático, incluí un index.html o usá --framework static.', 'red');
|
|
482
|
+
process.exit(1);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
486
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
487
|
+
|
|
488
|
+
if (deps.vite || hasAnyFile(['vite.config.js', 'vite.config.mjs', 'vite.config.ts', 'vite.config.cjs'])) {
|
|
489
|
+
return { ...FRAMEWORK_CONFIGS.vite };
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
if (deps.next || hasAnyFile(['next.config.js', 'next.config.mjs', 'next.config.ts'])) {
|
|
493
|
+
return { ...FRAMEWORK_CONFIGS.next };
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
if (deps['react-scripts']) {
|
|
497
|
+
return { ...FRAMEWORK_CONFIGS.react };
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
if (deps['@angular/core'] || existsSync(join(process.cwd(), 'angular.json'))) {
|
|
501
|
+
return { ...FRAMEWORK_CONFIGS.angular };
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
if (deps.nuxt || hasAnyFile(['nuxt.config.js', 'nuxt.config.ts'])) {
|
|
505
|
+
return { ...FRAMEWORK_CONFIGS.nuxt };
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
if (deps.astro || hasAnyFile(['astro.config.mjs', 'astro.config.js', 'astro.config.ts', 'astro.config.cjs'])) {
|
|
509
|
+
return { ...FRAMEWORK_CONFIGS.astro };
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
if (pkg.scripts?.build) {
|
|
513
|
+
return { name: 'custom', buildScript: 'build', distCandidates: ['dist', 'build', 'out'] };
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
const existingOutputDir = findFirstExistingDirectory(['dist', 'build', 'out']);
|
|
517
|
+
if (existingOutputDir) {
|
|
518
|
+
return { ...FRAMEWORK_CONFIGS.static, distCandidates: [existingOutputDir, '.'] };
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
if (existsSync(join(process.cwd(), 'index.html'))) {
|
|
522
|
+
return { ...FRAMEWORK_CONFIGS.static, distCandidates: ['.'] };
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
log('❌ No se pudo detectar framework ni directorio de salida. Usá --framework o creá script build.', 'red');
|
|
526
|
+
process.exit(1);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
function hasAnyFile(fileNames) {
|
|
530
|
+
return fileNames.some((fileName) => existsSync(join(process.cwd(), fileName)));
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
function findFirstExistingDirectory(candidates) {
|
|
534
|
+
for (const candidate of candidates) {
|
|
535
|
+
if (existsSync(join(process.cwd(), candidate))) {
|
|
536
|
+
return candidate;
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
return null;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
function detectPackageManager() {
|
|
544
|
+
if (existsSync(join(process.cwd(), 'bun.lockb')) || existsSync(join(process.cwd(), 'bun.lock'))) {
|
|
545
|
+
return 'bun';
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
if (existsSync(join(process.cwd(), 'pnpm-lock.yaml'))) {
|
|
549
|
+
return 'pnpm';
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
if (existsSync(join(process.cwd(), 'yarn.lock'))) {
|
|
553
|
+
return 'yarn';
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
if (existsSync(join(process.cwd(), 'package-lock.json'))) {
|
|
557
|
+
return 'npm';
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
return 'npm';
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
function buildCommandForScript(packageManager, scriptName) {
|
|
564
|
+
if (packageManager === 'yarn') {
|
|
565
|
+
return `yarn ${scriptName}`;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
if (packageManager === 'pnpm') {
|
|
569
|
+
return `pnpm ${scriptName}`;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
if (packageManager === 'bun') {
|
|
573
|
+
return `bun run ${scriptName}`;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
return `npm run ${scriptName}`;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
function resolveDistPath(framework) {
|
|
580
|
+
for (const candidate of framework.distCandidates) {
|
|
581
|
+
if (candidate === '.') {
|
|
582
|
+
if (existsSync(join(process.cwd(), 'index.html'))) {
|
|
583
|
+
return process.cwd();
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
continue;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
const distPath = join(process.cwd(), candidate);
|
|
590
|
+
|
|
591
|
+
if (existsSync(distPath)) {
|
|
592
|
+
return distPath;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
if (framework.name === 'next') {
|
|
597
|
+
throw new Error('No se encontró el directorio "out" de Next.js. Configurá `output: "export"` en next.config.js para export estático y volvé a ejecutar el build.');
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
if (framework.name === 'custom') {
|
|
601
|
+
throw new Error('Build ejecutado, pero no se encontró salida en dist/, build/ u out/. Forzá --framework o ajustá tu output.');
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
throw new Error(`No se encontró el directorio de salida (${framework.distCandidates.join(', ')})`);
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
async function zipDirectory(dirPath) {
|
|
608
|
+
const zipPath = join(process.cwd(), 'socc-deploy.zip');
|
|
609
|
+
|
|
610
|
+
return new Promise((resolve, reject) => {
|
|
611
|
+
const output = createWriteStream(zipPath);
|
|
612
|
+
const archive = archiver('zip', { zlib: { level: 9 } });
|
|
613
|
+
|
|
614
|
+
output.on('close', () => resolve(zipPath));
|
|
615
|
+
archive.on('error', reject);
|
|
616
|
+
|
|
617
|
+
archive.pipe(output);
|
|
618
|
+
archive.directory(dirPath, false, (entry) => {
|
|
619
|
+
if (
|
|
620
|
+
entry.name.includes('node_modules')
|
|
621
|
+
|| entry.name.includes('.git')
|
|
622
|
+
|| entry.name.includes('.next')
|
|
623
|
+
|| entry.name === 'socc-deploy.zip'
|
|
624
|
+
) {
|
|
625
|
+
return false;
|
|
626
|
+
}
|
|
627
|
+
return entry;
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
archive.finalize();
|
|
631
|
+
});
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
async function uploadToApi(zipPath, token, options) {
|
|
635
|
+
const fileBuffer = readFileSync(zipPath);
|
|
636
|
+
|
|
637
|
+
const response = await fetch(`${API_URL}/api/link`, {
|
|
638
|
+
method: 'POST',
|
|
639
|
+
headers: {
|
|
640
|
+
Authorization: `Bearer ${token}`,
|
|
641
|
+
'Content-Type': 'application/zip',
|
|
642
|
+
'X-TTL': `${options.ttl}`,
|
|
643
|
+
'X-Framework': options.framework
|
|
644
|
+
},
|
|
645
|
+
body: new Uint8Array(fileBuffer)
|
|
646
|
+
});
|
|
647
|
+
|
|
648
|
+
return response.json();
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
// ============= MAIN =============
|
|
652
|
+
|
|
653
|
+
async function main() {
|
|
654
|
+
const args = process.argv.slice(2);
|
|
655
|
+
const command = args[0];
|
|
656
|
+
|
|
657
|
+
switch (command) {
|
|
658
|
+
case undefined:
|
|
659
|
+
case 'help':
|
|
660
|
+
case '--help':
|
|
661
|
+
case '-h':
|
|
662
|
+
showHelp();
|
|
663
|
+
break;
|
|
664
|
+
case 'login':
|
|
665
|
+
await cmdLogin(args.slice(1));
|
|
666
|
+
break;
|
|
667
|
+
case 'whoami':
|
|
668
|
+
await cmdWhoami();
|
|
669
|
+
break;
|
|
670
|
+
case 'link':
|
|
671
|
+
await cmdLink(args.slice(1));
|
|
672
|
+
break;
|
|
673
|
+
case 'list':
|
|
674
|
+
await cmdList();
|
|
675
|
+
break;
|
|
676
|
+
case 'delete':
|
|
677
|
+
await cmdDelete(args.slice(1));
|
|
678
|
+
break;
|
|
679
|
+
default:
|
|
680
|
+
log(`❌ Comando desconocido: ${command}`, 'red');
|
|
681
|
+
log('Usá `socc help` para ver los comandos disponibles', 'yellow');
|
|
682
|
+
process.exit(1);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
main().catch((error) => {
|
|
687
|
+
console.error(error);
|
|
688
|
+
process.exit(1);
|
|
689
|
+
});
|