@soccagency/sh 0.2.0 → 0.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/README.md +54 -188
- package/package.json +1 -1
- package/src/index.js +128 -107
package/README.md
CHANGED
|
@@ -1,242 +1,108 @@
|
|
|
1
1
|
# Socc CLI
|
|
2
2
|
|
|
3
|
-
CLI
|
|
3
|
+
CLI for publishing instant preview links with Socc.
|
|
4
4
|
|
|
5
|
-
##
|
|
6
|
-
|
|
7
|
-
- **Runtime:** Node.js 20+
|
|
8
|
-
- **Package:** npm
|
|
9
|
-
- **Comprimiendo:** archiver
|
|
10
|
-
- **HTTP:** undici (fetch nativo)
|
|
11
|
-
|
|
12
|
-
## 📋 Requisitos
|
|
5
|
+
## Requirements
|
|
13
6
|
|
|
14
7
|
- Node.js 20+
|
|
15
|
-
- npm
|
|
16
|
-
- Token de Socc (obtenido desde sh.socc.ink)
|
|
8
|
+
- npm account + API token from `https://sh.socc.ink`
|
|
17
9
|
|
|
18
|
-
##
|
|
10
|
+
## Install
|
|
19
11
|
|
|
20
|
-
###
|
|
12
|
+
### From npm (public package)
|
|
21
13
|
|
|
22
14
|
```bash
|
|
23
|
-
npm
|
|
15
|
+
npm i -g @soccagency/sh
|
|
24
16
|
```
|
|
25
17
|
|
|
26
|
-
###
|
|
18
|
+
### From source (local development)
|
|
27
19
|
|
|
28
20
|
```bash
|
|
29
|
-
# Clonar o navegar al directorio del CLI
|
|
30
21
|
cd /home/clawdbot/projects/socc/cli
|
|
31
|
-
|
|
32
|
-
# Instalar dependencias
|
|
33
22
|
npm install
|
|
34
|
-
|
|
35
|
-
# Instalar globalmente (desarrollo)
|
|
36
23
|
npm install -g .
|
|
37
24
|
```
|
|
38
25
|
|
|
39
|
-
##
|
|
40
|
-
|
|
41
|
-
### `socc login <token>`
|
|
42
|
-
|
|
43
|
-
Inicia sesión con tu API token.
|
|
26
|
+
## Quickstart
|
|
44
27
|
|
|
45
28
|
```bash
|
|
46
|
-
|
|
47
|
-
|
|
29
|
+
# 1) Authenticate once
|
|
30
|
+
socc login socc_xxxxx
|
|
48
31
|
|
|
49
|
-
|
|
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
|
|
32
|
+
# 2) Deploy from your project folder
|
|
63
33
|
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
34
|
|
|
91
|
-
|
|
35
|
+
# 3) Optional: set TTL and framework explicitly
|
|
36
|
+
socc link --ttl 1 --framework vite
|
|
92
37
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
```bash
|
|
38
|
+
# 4) List and delete previews
|
|
96
39
|
socc list
|
|
40
|
+
socc delete <deployment-id>
|
|
97
41
|
```
|
|
98
42
|
|
|
99
|
-
|
|
100
|
-
```
|
|
101
|
-
📋 Obteniendo tus deployments...
|
|
43
|
+
## Commands
|
|
102
44
|
|
|
103
|
-
|
|
45
|
+
- `socc login <token>`: Save API token locally.
|
|
46
|
+
- `socc logout`: Remove saved API token.
|
|
47
|
+
- `socc whoami`: Show account plan and usage limits.
|
|
48
|
+
- `socc link [--ttl <hours>] [--framework <name>]`: Build, package, and deploy.
|
|
49
|
+
- `socc list`: List active deployments.
|
|
50
|
+
- `socc delete <id>`: Hard-delete a deployment.
|
|
51
|
+
- `socc help`: Show command help.
|
|
104
52
|
|
|
105
|
-
|
|
106
|
-
URL: https://emqg9i7y.socc.ink
|
|
107
|
-
Framework: static
|
|
108
|
-
Tamaño: 0.01 MB
|
|
109
|
-
Expira en: 5h
|
|
53
|
+
## Link Options
|
|
110
54
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
Framework: react
|
|
114
|
-
Tamaño: 1.23 MB
|
|
115
|
-
Expira en: 23h
|
|
116
|
-
```
|
|
55
|
+
- `--ttl <hours>`: Positive integer in hours. Default is `6`.
|
|
56
|
+
- `--framework <name>`: `auto|vite|react|next|angular|nuxt|astro|static`.
|
|
117
57
|
|
|
118
|
-
|
|
58
|
+
If you do not pass `--framework`, detection runs automatically.
|
|
119
59
|
|
|
120
|
-
|
|
60
|
+
## Auto Detection
|
|
121
61
|
|
|
122
|
-
|
|
123
|
-
socc delete emqg9i7y
|
|
124
|
-
```
|
|
62
|
+
The CLI detects:
|
|
125
63
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
🗑️ Borrando deployment emqg9i7y...
|
|
129
|
-
✅ Deployment emqg9i7y eliminado.
|
|
130
|
-
```
|
|
64
|
+
- Framework: `vite`, `react`, `next`, `angular`, `nuxt`, `astro`, `static`, or custom build script.
|
|
65
|
+
- Package manager: `npm`, `yarn`, `pnpm`, or `bun` (by lockfile).
|
|
131
66
|
|
|
132
|
-
###
|
|
67
|
+
### Next.js note
|
|
133
68
|
|
|
134
|
-
|
|
69
|
+
Socc requires static export output (`out/`) for Next.js.
|
|
135
70
|
|
|
136
|
-
|
|
137
|
-
socc whoami
|
|
138
|
-
```
|
|
71
|
+
Set this in `next.config.js`:
|
|
139
72
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
📈 Deployments activos: 0/1
|
|
145
|
-
📅 Deployments hoy: 2/5
|
|
146
|
-
📦 Total links: 15
|
|
73
|
+
```js
|
|
74
|
+
export default {
|
|
75
|
+
output: 'export'
|
|
76
|
+
};
|
|
147
77
|
```
|
|
148
78
|
|
|
149
|
-
|
|
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:
|
|
79
|
+
Then run `socc link` again.
|
|
153
80
|
|
|
154
|
-
|
|
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/` |
|
|
81
|
+
## Environment Variable
|
|
163
82
|
|
|
164
|
-
|
|
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"
|
|
83
|
+
You can override the API URL:
|
|
183
84
|
|
|
184
85
|
```bash
|
|
185
|
-
socc
|
|
86
|
+
SOCC_API_URL=http://localhost:3000 socc whoami
|
|
186
87
|
```
|
|
187
88
|
|
|
188
|
-
|
|
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
|
|
89
|
+
Default: `https://api.socc.ink`
|
|
207
90
|
|
|
208
|
-
##
|
|
91
|
+
## Auth Storage
|
|
209
92
|
|
|
210
|
-
|
|
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:
|
|
93
|
+
Token is saved in:
|
|
224
94
|
|
|
225
95
|
```bash
|
|
226
|
-
|
|
96
|
+
~/.socc/config.json
|
|
227
97
|
```
|
|
228
98
|
|
|
229
|
-
|
|
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
|
-
```
|
|
99
|
+
- Switch account: run `socc login <new-token>` (replaces current token).
|
|
100
|
+
- Logout: run `socc logout`.
|
|
239
101
|
|
|
240
|
-
|
|
102
|
+
## Troubleshooting
|
|
241
103
|
|
|
242
|
-
|
|
104
|
+
- `You are not authenticated`: run `socc login <token>`.
|
|
105
|
+
- `Token is invalid or expired`: generate a new token in `https://sh.socc.ink`.
|
|
106
|
+
- `Daily deployment limit reached`: wait for reset or upgrade plan.
|
|
107
|
+
- `Build failed`: verify local build command works before running `socc link`.
|
|
108
|
+
- `Output directory not found`: set framework explicitly or fix build output path.
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -21,7 +21,7 @@ const FRAMEWORK_CONFIGS = {
|
|
|
21
21
|
static: { name: 'static', buildScript: null, distCandidates: ['dist', '.'] }
|
|
22
22
|
};
|
|
23
23
|
|
|
24
|
-
//
|
|
24
|
+
// Colors
|
|
25
25
|
const colors = {
|
|
26
26
|
reset: '\x1b[0m',
|
|
27
27
|
red: '\x1b[31m',
|
|
@@ -37,39 +37,40 @@ function log(msg, color = 'reset') {
|
|
|
37
37
|
console.log(`${colors[color]}${msg}${colors.reset}`);
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
// =============
|
|
40
|
+
// ============= COMMANDS =============
|
|
41
41
|
|
|
42
42
|
function showHelp() {
|
|
43
43
|
log(`
|
|
44
|
-
🚀 Socc CLI - Deploy
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
socc
|
|
48
|
-
socc link [--ttl <
|
|
49
|
-
socc list
|
|
50
|
-
socc delete <id>
|
|
51
|
-
socc login <token>
|
|
52
|
-
socc
|
|
53
|
-
socc
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
--
|
|
58
|
-
|
|
59
|
-
|
|
44
|
+
🚀 Socc CLI - Instant Deploy
|
|
45
|
+
|
|
46
|
+
Usage:
|
|
47
|
+
socc Show help
|
|
48
|
+
socc link [--ttl <hours>] [--framework <${ALLOWED_FRAMEWORKS.join('|')}>]
|
|
49
|
+
socc list List deployments
|
|
50
|
+
socc delete <id> Delete deployment
|
|
51
|
+
socc login <token> Authenticate with API token
|
|
52
|
+
socc logout Remove saved API token
|
|
53
|
+
socc whoami Show account info
|
|
54
|
+
socc help Show help
|
|
55
|
+
|
|
56
|
+
Link flags:
|
|
57
|
+
--ttl <hours> TTL in hours (positive integer, default 6)
|
|
58
|
+
--framework <name> Force framework: auto|vite|react|next|angular|nuxt|astro|static
|
|
59
|
+
|
|
60
|
+
Environment variables:
|
|
60
61
|
SOCC_API_URL API base URL (default: https://api.socc.ink)
|
|
61
62
|
|
|
62
|
-
|
|
63
|
+
Examples:
|
|
63
64
|
$ socc login socc_abcdef123...
|
|
64
65
|
$ socc link --ttl 1
|
|
65
66
|
$ socc link --framework vite
|
|
66
67
|
$ SOCC_API_URL=http://localhost:3000 socc whoami
|
|
67
68
|
|
|
68
|
-
|
|
69
|
-
Free - 1 deployment
|
|
70
|
-
Starter - 5 deployments
|
|
71
|
-
Pro - 20 deployments
|
|
72
|
-
Enterprise -
|
|
69
|
+
Plans:
|
|
70
|
+
Free - 1 active deployment, 5/day, 10MB max
|
|
71
|
+
Starter - 5 active deployments, 50/day, 50MB max
|
|
72
|
+
Pro - 20 active deployments, 200/day, 100MB max
|
|
73
|
+
Enterprise - Unlimited
|
|
73
74
|
`, 'cyan');
|
|
74
75
|
}
|
|
75
76
|
|
|
@@ -78,24 +79,24 @@ async function cmdLogin(args) {
|
|
|
78
79
|
|
|
79
80
|
if (!token) {
|
|
80
81
|
log('\n🔐 Socc Login\n', 'cyan');
|
|
81
|
-
log('
|
|
82
|
-
log('📋
|
|
83
|
-
log('1.
|
|
84
|
-
log('2.
|
|
85
|
-
log('3. Click
|
|
86
|
-
log('4.
|
|
82
|
+
log('Usage: socc login <your-api-token>\n', 'white');
|
|
83
|
+
log('📋 How to get your API token:', 'yellow');
|
|
84
|
+
log('1. Go to https://sh.socc.ink and sign in with GitHub', 'yellow');
|
|
85
|
+
log('2. Open your account settings', 'yellow');
|
|
86
|
+
log('3. Click "Generate API token"', 'yellow');
|
|
87
|
+
log('4. Copy the token and run: socc login socc_xxxxx\n', 'yellow');
|
|
87
88
|
process.exit(0);
|
|
88
89
|
}
|
|
89
90
|
|
|
90
|
-
log('🔐
|
|
91
|
+
log('🔐 Verifying token...', 'yellow');
|
|
91
92
|
const userInfo = await getUserInfo(token);
|
|
92
93
|
|
|
93
94
|
if (!userInfo.success) {
|
|
94
|
-
log(`❌
|
|
95
|
+
log(`❌ Invalid token: ${userInfo.error}`, 'red');
|
|
95
96
|
process.exit(1);
|
|
96
97
|
}
|
|
97
98
|
|
|
98
|
-
//
|
|
99
|
+
// Save token
|
|
99
100
|
const configDir = join(process.env.HOME, '.socc');
|
|
100
101
|
const configPath = join(configDir, 'config.json');
|
|
101
102
|
|
|
@@ -105,17 +106,17 @@ async function cmdLogin(args) {
|
|
|
105
106
|
|
|
106
107
|
writeFileSync(configPath, JSON.stringify({ token }, null, 2));
|
|
107
108
|
|
|
108
|
-
log('✅
|
|
109
|
-
log(`\n👤
|
|
110
|
-
log(`📊
|
|
111
|
-
log('\n🚀
|
|
109
|
+
log('✅ Login successful!', 'green');
|
|
110
|
+
log(`\n👤 Authenticated as: ${userInfo.user.email}`, 'white');
|
|
111
|
+
log(`📊 Tier: ${userInfo.user.tier.toUpperCase()}`, 'blue');
|
|
112
|
+
log('\n🚀 You can now run: socc link\n', 'cyan');
|
|
112
113
|
}
|
|
113
114
|
|
|
114
115
|
async function cmdWhoami() {
|
|
115
116
|
const token = getToken();
|
|
116
117
|
|
|
117
118
|
if (!token) {
|
|
118
|
-
log('❌
|
|
119
|
+
log('❌ You are not authenticated. Run `socc login` first.', 'red');
|
|
119
120
|
process.exit(1);
|
|
120
121
|
}
|
|
121
122
|
|
|
@@ -128,13 +129,13 @@ async function cmdWhoami() {
|
|
|
128
129
|
|
|
129
130
|
const { user, usage } = userInfo;
|
|
130
131
|
|
|
131
|
-
log('\n👤
|
|
132
|
+
log('\n👤 Your Socc account\n', 'cyan');
|
|
132
133
|
log(`Email: ${user.email}`, 'white');
|
|
133
|
-
log(`
|
|
134
|
-
log(`
|
|
135
|
-
log('\n📊
|
|
136
|
-
log(`
|
|
137
|
-
log(` Deployments
|
|
134
|
+
log(`Tier: ${user.tier.toUpperCase()}`, 'blue');
|
|
135
|
+
log(`Status: ${user.subscriptionStatus || 'active'}`, 'green');
|
|
136
|
+
log('\n📊 Usage:', 'yellow');
|
|
137
|
+
log(` Active deployments: ${usage.activeDeployments}/${usage.maxActiveDeployments}`, 'white');
|
|
138
|
+
log(` Deployments today: ${usage.linksToday}/${usage.linksLimit}`, 'white');
|
|
138
139
|
log(` Max upload: ${usage.maxUpload}`, 'white');
|
|
139
140
|
log(` Max TTL: ${usage.maxTtl}`, 'white');
|
|
140
141
|
log(`\n📈 Total deployments: ${usage.totalLinks}\n`, 'gray');
|
|
@@ -144,11 +145,11 @@ async function cmdList() {
|
|
|
144
145
|
const token = getToken();
|
|
145
146
|
|
|
146
147
|
if (!token) {
|
|
147
|
-
log('❌
|
|
148
|
+
log('❌ You are not authenticated. Run `socc login` first.', 'red');
|
|
148
149
|
process.exit(1);
|
|
149
150
|
}
|
|
150
151
|
|
|
151
|
-
log('📋
|
|
152
|
+
log('📋 Fetching your deployments...', 'yellow');
|
|
152
153
|
|
|
153
154
|
try {
|
|
154
155
|
const response = await fetch(`${API_URL}/api/list`, {
|
|
@@ -163,12 +164,12 @@ async function cmdList() {
|
|
|
163
164
|
}
|
|
164
165
|
|
|
165
166
|
if (data.deployments.length === 0) {
|
|
166
|
-
log('\n📭 No
|
|
167
|
-
log('
|
|
167
|
+
log('\n📭 No active deployments found.', 'gray');
|
|
168
|
+
log('Create your first deploy with: socc link\n', 'cyan');
|
|
168
169
|
return;
|
|
169
170
|
}
|
|
170
171
|
|
|
171
|
-
log('\n📦
|
|
172
|
+
log('\n📦 Your deployments:', 'cyan');
|
|
172
173
|
|
|
173
174
|
data.deployments.forEach((d, i) => {
|
|
174
175
|
const expires = new Date(d.expiresAt);
|
|
@@ -183,8 +184,8 @@ async function cmdList() {
|
|
|
183
184
|
console.log(` ${i + 1}. ${status} ${id}`);
|
|
184
185
|
console.log(` URL: https://${subdomain}.socc.ink`);
|
|
185
186
|
console.log(` Framework: ${framework}`);
|
|
186
|
-
console.log(`
|
|
187
|
-
console.log(`
|
|
187
|
+
console.log(` Size: ${sizeMB} MB`);
|
|
188
|
+
console.log(` Expires in: ${hoursLeft > 0 ? `${hoursLeft}h` : 'EXPIRED'}`);
|
|
188
189
|
console.log('');
|
|
189
190
|
});
|
|
190
191
|
|
|
@@ -198,20 +199,20 @@ async function cmdDelete(args) {
|
|
|
198
199
|
const projectId = args[0];
|
|
199
200
|
|
|
200
201
|
if (!projectId) {
|
|
201
|
-
log('\n❌
|
|
202
|
-
log('\
|
|
203
|
-
log('
|
|
202
|
+
log('\n❌ You must provide a deployment ID.', 'red');
|
|
203
|
+
log('\nUsage: socc delete <project-id>\n', 'yellow');
|
|
204
|
+
log('Get the ID with: socc list\n', 'cyan');
|
|
204
205
|
process.exit(1);
|
|
205
206
|
}
|
|
206
207
|
|
|
207
208
|
const token = getToken();
|
|
208
209
|
|
|
209
210
|
if (!token) {
|
|
210
|
-
log('❌
|
|
211
|
+
log('❌ You are not authenticated. Run `socc login` first.', 'red');
|
|
211
212
|
process.exit(1);
|
|
212
213
|
}
|
|
213
214
|
|
|
214
|
-
log(`🗑️
|
|
215
|
+
log(`🗑️ Deleting deployment ${projectId}...`, 'yellow');
|
|
215
216
|
|
|
216
217
|
try {
|
|
217
218
|
const response = await fetch(`${API_URL}/api/delete/${projectId}`, {
|
|
@@ -226,7 +227,7 @@ async function cmdDelete(args) {
|
|
|
226
227
|
process.exit(1);
|
|
227
228
|
}
|
|
228
229
|
|
|
229
|
-
log(`✅ Deployment ${projectId}
|
|
230
|
+
log(`✅ Deployment ${projectId} deleted.\n`, 'green');
|
|
230
231
|
|
|
231
232
|
} catch (error) {
|
|
232
233
|
log(`❌ Error: ${error.message}`, 'red');
|
|
@@ -239,24 +240,24 @@ async function cmdLink(args) {
|
|
|
239
240
|
|
|
240
241
|
if (!parsedOptions.ok) {
|
|
241
242
|
log(`❌ ${parsedOptions.error}`, 'red');
|
|
242
|
-
log(`\
|
|
243
|
+
log(`\nUsage: socc link [--ttl <hours>] [--framework <${ALLOWED_FRAMEWORKS.join('|')}>]\n`, 'yellow');
|
|
243
244
|
process.exit(1);
|
|
244
245
|
}
|
|
245
246
|
|
|
246
247
|
const options = parsedOptions.options;
|
|
247
248
|
|
|
248
|
-
log('🚀 Socc -
|
|
249
|
+
log('🚀 Socc - Instant deploy\n', 'cyan');
|
|
249
250
|
|
|
250
251
|
const token = getToken();
|
|
251
252
|
|
|
252
253
|
if (!token) {
|
|
253
|
-
log('❌
|
|
254
|
-
log('\
|
|
254
|
+
log('❌ You are not authenticated. Run `socc login` first.', 'red');
|
|
255
|
+
log('\nTo authenticate: socc login socc_xxxxx\n', 'cyan');
|
|
255
256
|
process.exit(1);
|
|
256
257
|
}
|
|
257
258
|
|
|
258
|
-
//
|
|
259
|
-
log('🔐
|
|
259
|
+
// Check limits
|
|
260
|
+
log('🔐 Checking account...', 'yellow');
|
|
260
261
|
const userInfo = await getUserInfo(token);
|
|
261
262
|
|
|
262
263
|
if (!userInfo.success) {
|
|
@@ -266,42 +267,42 @@ async function cmdLink(args) {
|
|
|
266
267
|
|
|
267
268
|
const { user, usage, canCreateLink } = userInfo;
|
|
268
269
|
|
|
269
|
-
log(`✅
|
|
270
|
-
log(`📊
|
|
271
|
-
log(`📈
|
|
272
|
-
log(`📅 Deployments
|
|
270
|
+
log(`✅ Authenticated as: ${user.email}`, 'green');
|
|
271
|
+
log(`📊 Tier: ${user.tier.toUpperCase()}`, 'blue');
|
|
272
|
+
log(`📈 Active deployments: ${usage.activeDeployments}/${usage.maxActiveDeployments}`, 'blue');
|
|
273
|
+
log(`📅 Deployments today: ${usage.linksToday}/${usage.linksLimit}`, 'blue');
|
|
273
274
|
|
|
274
275
|
if (!canCreateLink) {
|
|
275
|
-
log('\n❌
|
|
276
|
-
log(`
|
|
277
|
-
log('
|
|
276
|
+
log('\n❌ Daily deployment limit reached.', 'red');
|
|
277
|
+
log(`Your ${user.tier} plan allows ${usage.linksLimit} deployments per day.`, 'yellow');
|
|
278
|
+
log('Try again tomorrow or upgrade your plan.\n', 'yellow');
|
|
278
279
|
process.exit(1);
|
|
279
280
|
}
|
|
280
281
|
|
|
281
282
|
if (usage.activeDeployments >= usage.maxActiveDeployments && usage.maxActiveDeployments !== 'unlimited') {
|
|
282
|
-
log('\n❌
|
|
283
|
-
log(`
|
|
284
|
-
log('
|
|
285
|
-
log('
|
|
283
|
+
log('\n❌ Maximum active deployments reached.', 'red');
|
|
284
|
+
log(`Your ${user.tier} plan allows ${usage.maxActiveDeployments} active deployments.`, 'yellow');
|
|
285
|
+
log('Delete old previews with: socc delete <id>', 'yellow');
|
|
286
|
+
log('Or upgrade your plan.\n', 'yellow');
|
|
286
287
|
process.exit(1);
|
|
287
288
|
}
|
|
288
289
|
|
|
289
|
-
//
|
|
290
|
+
// Detect framework
|
|
290
291
|
const framework = detectFramework(options.framework);
|
|
291
292
|
const packageManager = detectPackageManager();
|
|
292
|
-
log(`📦 Framework
|
|
293
|
-
log(`📦 Package manager
|
|
294
|
-
log(`⏰ TTL
|
|
293
|
+
log(`📦 Framework: ${framework.name}`, 'blue');
|
|
294
|
+
log(`📦 Package manager: ${packageManager}`, 'blue');
|
|
295
|
+
log(`⏰ Requested TTL: ${options.ttl} hour(s)`, 'blue');
|
|
295
296
|
|
|
296
297
|
// Build
|
|
297
298
|
if (framework.buildScript) {
|
|
298
299
|
const buildCommand = buildCommandForScript(packageManager, framework.buildScript);
|
|
299
|
-
log(`🔨
|
|
300
|
+
log(`🔨 Building project (${buildCommand})...`, 'yellow');
|
|
300
301
|
try {
|
|
301
302
|
execSync(buildCommand, { stdio: 'inherit' });
|
|
302
|
-
log('✅ Build
|
|
303
|
+
log('✅ Build completed', 'green');
|
|
303
304
|
} catch {
|
|
304
|
-
log('❌ Build
|
|
305
|
+
log('❌ Build failed', 'red');
|
|
305
306
|
process.exit(1);
|
|
306
307
|
}
|
|
307
308
|
}
|
|
@@ -314,13 +315,13 @@ async function cmdLink(args) {
|
|
|
314
315
|
process.exit(1);
|
|
315
316
|
}
|
|
316
317
|
|
|
317
|
-
//
|
|
318
|
-
log('📦
|
|
318
|
+
// Compress files
|
|
319
|
+
log('📦 Compressing files...', 'yellow');
|
|
319
320
|
const zipPath = await zipDirectory(distPath);
|
|
320
|
-
log('✅
|
|
321
|
+
log('✅ Files compressed', 'green');
|
|
321
322
|
|
|
322
323
|
// Upload
|
|
323
|
-
log('📤
|
|
324
|
+
log('📤 Uploading to Socc...', 'yellow');
|
|
324
325
|
try {
|
|
325
326
|
const result = await uploadToApi(zipPath, token, {
|
|
326
327
|
ttl: options.ttl,
|
|
@@ -328,11 +329,11 @@ async function cmdLink(args) {
|
|
|
328
329
|
});
|
|
329
330
|
|
|
330
331
|
if (result.success && result.url) {
|
|
331
|
-
log('\n✅
|
|
332
|
-
log(`\n🚀
|
|
333
|
-
log(`⏰
|
|
332
|
+
log('\n✅ Deploy successful!', 'green');
|
|
333
|
+
log(`\n🚀 Your preview is live at: ${result.url}\n`, 'cyan');
|
|
334
|
+
log(`⏰ Expires in: ${result.ttl} hour(s)\n`, 'blue');
|
|
334
335
|
} else {
|
|
335
|
-
log('❌
|
|
336
|
+
log('❌ Deployment failed', 'red');
|
|
336
337
|
if (result.error) {
|
|
337
338
|
log(result.error, 'red');
|
|
338
339
|
}
|
|
@@ -343,7 +344,7 @@ async function cmdLink(args) {
|
|
|
343
344
|
process.exit(1);
|
|
344
345
|
}
|
|
345
346
|
|
|
346
|
-
//
|
|
347
|
+
// Cleanup
|
|
347
348
|
try {
|
|
348
349
|
unlinkSync(zipPath);
|
|
349
350
|
} catch {
|
|
@@ -351,6 +352,23 @@ async function cmdLink(args) {
|
|
|
351
352
|
}
|
|
352
353
|
}
|
|
353
354
|
|
|
355
|
+
function cmdLogout() {
|
|
356
|
+
const configPath = join(process.env.HOME, '.socc', 'config.json');
|
|
357
|
+
|
|
358
|
+
if (!existsSync(configPath)) {
|
|
359
|
+
log('ℹ️ No active session found.', 'yellow');
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
try {
|
|
364
|
+
unlinkSync(configPath);
|
|
365
|
+
log('✅ Logged out successfully.\n', 'green');
|
|
366
|
+
} catch (error) {
|
|
367
|
+
log(`❌ Failed to logout: ${error.message}`, 'red');
|
|
368
|
+
process.exit(1);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
354
372
|
// ============= HELPERS =============
|
|
355
373
|
|
|
356
374
|
function getToken() {
|
|
@@ -374,13 +392,13 @@ async function getUserInfo(token) {
|
|
|
374
392
|
});
|
|
375
393
|
|
|
376
394
|
if (response.status === 401) {
|
|
377
|
-
return { success: false, error: 'Token
|
|
395
|
+
return { success: false, error: 'Token is invalid or expired' };
|
|
378
396
|
}
|
|
379
397
|
|
|
380
398
|
const data = await response.json();
|
|
381
399
|
|
|
382
400
|
if (!data || !data.success) {
|
|
383
|
-
return { success: false, error: data?.error || '
|
|
401
|
+
return { success: false, error: data?.error || 'Failed to verify user' };
|
|
384
402
|
}
|
|
385
403
|
|
|
386
404
|
return {
|
|
@@ -391,7 +409,7 @@ async function getUserInfo(token) {
|
|
|
391
409
|
canCreateLink: data.canCreateLink !== false
|
|
392
410
|
};
|
|
393
411
|
} catch (error) {
|
|
394
|
-
return { success: false, error: error.message || '
|
|
412
|
+
return { success: false, error: error.message || 'Connection error' };
|
|
395
413
|
}
|
|
396
414
|
}
|
|
397
415
|
|
|
@@ -405,12 +423,12 @@ function parseLinkOptions(args) {
|
|
|
405
423
|
if (arg === '--ttl') {
|
|
406
424
|
const ttlValue = args[i + 1];
|
|
407
425
|
if (!ttlValue) {
|
|
408
|
-
return { ok: false, error: '
|
|
426
|
+
return { ok: false, error: 'Missing value for --ttl' };
|
|
409
427
|
}
|
|
410
428
|
|
|
411
429
|
const parsedTtl = Number.parseInt(ttlValue, 10);
|
|
412
430
|
if (!Number.isInteger(parsedTtl) || parsedTtl <= 0) {
|
|
413
|
-
return { ok: false, error: '--ttl
|
|
431
|
+
return { ok: false, error: '--ttl must be a positive integer' };
|
|
414
432
|
}
|
|
415
433
|
|
|
416
434
|
ttl = parsedTtl;
|
|
@@ -422,7 +440,7 @@ function parseLinkOptions(args) {
|
|
|
422
440
|
const ttlValue = arg.split('=')[1];
|
|
423
441
|
const parsedTtl = Number.parseInt(ttlValue, 10);
|
|
424
442
|
if (!Number.isInteger(parsedTtl) || parsedTtl <= 0) {
|
|
425
|
-
return { ok: false, error: '--ttl
|
|
443
|
+
return { ok: false, error: '--ttl must be a positive integer' };
|
|
426
444
|
}
|
|
427
445
|
|
|
428
446
|
ttl = parsedTtl;
|
|
@@ -432,11 +450,11 @@ function parseLinkOptions(args) {
|
|
|
432
450
|
if (arg === '--framework') {
|
|
433
451
|
const frameworkValue = args[i + 1];
|
|
434
452
|
if (!frameworkValue) {
|
|
435
|
-
return { ok: false, error: '
|
|
453
|
+
return { ok: false, error: 'Missing value for --framework' };
|
|
436
454
|
}
|
|
437
455
|
|
|
438
456
|
if (!ALLOWED_FRAMEWORKS.includes(frameworkValue)) {
|
|
439
|
-
return { ok: false, error: `
|
|
457
|
+
return { ok: false, error: `Invalid framework: ${frameworkValue}` };
|
|
440
458
|
}
|
|
441
459
|
|
|
442
460
|
framework = frameworkValue;
|
|
@@ -447,14 +465,14 @@ function parseLinkOptions(args) {
|
|
|
447
465
|
if (arg.startsWith('--framework=')) {
|
|
448
466
|
const frameworkValue = arg.split('=')[1];
|
|
449
467
|
if (!ALLOWED_FRAMEWORKS.includes(frameworkValue)) {
|
|
450
|
-
return { ok: false, error: `
|
|
468
|
+
return { ok: false, error: `Invalid framework: ${frameworkValue}` };
|
|
451
469
|
}
|
|
452
470
|
|
|
453
471
|
framework = frameworkValue;
|
|
454
472
|
continue;
|
|
455
473
|
}
|
|
456
474
|
|
|
457
|
-
return { ok: false, error: `
|
|
475
|
+
return { ok: false, error: `Unsupported flag: ${arg}` };
|
|
458
476
|
}
|
|
459
477
|
|
|
460
478
|
return {
|
|
@@ -478,7 +496,7 @@ function detectFramework(preferredFramework = 'auto') {
|
|
|
478
496
|
return { ...FRAMEWORK_CONFIGS.static, distCandidates: ['.'] };
|
|
479
497
|
}
|
|
480
498
|
|
|
481
|
-
log('❌
|
|
499
|
+
log('❌ package.json not found. If this is a static site, include index.html or use --framework static.', 'red');
|
|
482
500
|
process.exit(1);
|
|
483
501
|
}
|
|
484
502
|
|
|
@@ -522,7 +540,7 @@ function detectFramework(preferredFramework = 'auto') {
|
|
|
522
540
|
return { ...FRAMEWORK_CONFIGS.static, distCandidates: ['.'] };
|
|
523
541
|
}
|
|
524
542
|
|
|
525
|
-
log('❌
|
|
543
|
+
log('❌ Could not detect framework or output directory. Use --framework or add a build script.', 'red');
|
|
526
544
|
process.exit(1);
|
|
527
545
|
}
|
|
528
546
|
|
|
@@ -594,14 +612,14 @@ function resolveDistPath(framework) {
|
|
|
594
612
|
}
|
|
595
613
|
|
|
596
614
|
if (framework.name === 'next') {
|
|
597
|
-
throw new Error('
|
|
615
|
+
throw new Error('Next.js output directory "out" not found. Set `output: "export"` in next.config.js and run the build again.');
|
|
598
616
|
}
|
|
599
617
|
|
|
600
618
|
if (framework.name === 'custom') {
|
|
601
|
-
throw new Error('Build
|
|
619
|
+
throw new Error('Build finished, but no output was found in dist/, build/, or out/. Force --framework or adjust your output path.');
|
|
602
620
|
}
|
|
603
621
|
|
|
604
|
-
throw new Error(`
|
|
622
|
+
throw new Error(`Output directory not found (${framework.distCandidates.join(', ')})`);
|
|
605
623
|
}
|
|
606
624
|
|
|
607
625
|
async function zipDirectory(dirPath) {
|
|
@@ -664,6 +682,9 @@ async function main() {
|
|
|
664
682
|
case 'login':
|
|
665
683
|
await cmdLogin(args.slice(1));
|
|
666
684
|
break;
|
|
685
|
+
case 'logout':
|
|
686
|
+
cmdLogout();
|
|
687
|
+
break;
|
|
667
688
|
case 'whoami':
|
|
668
689
|
await cmdWhoami();
|
|
669
690
|
break;
|
|
@@ -677,8 +698,8 @@ async function main() {
|
|
|
677
698
|
await cmdDelete(args.slice(1));
|
|
678
699
|
break;
|
|
679
700
|
default:
|
|
680
|
-
log(`❌
|
|
681
|
-
log('
|
|
701
|
+
log(`❌ Unknown command: ${command}`, 'red');
|
|
702
|
+
log('Use `socc help` to see available commands', 'yellow');
|
|
682
703
|
process.exit(1);
|
|
683
704
|
}
|
|
684
705
|
}
|