shilo-cli 0.3.2 → 0.5.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 +73 -1
- package/bin/shilo.js +11 -4
- package/package.json +1 -1
- package/src/ShiloClient.js +121 -5
- package/src/args.js +4 -2
- package/src/commands/init.js +16 -5
- package/src/commands/login.js +13 -2
- package/src/commands/push.js +711 -164
- package/src/prompt.js +10 -2
- package/src/pushindex.js +151 -0
- package/src/remote.js +2 -1
- package/src/secrets.js +168 -0
- package/src/sync.js +86 -10
- package/src/textguard.js +44 -14
- package/src/token.js +72 -0
- package/src/ui.js +61 -1
- package/src/walk.js +142 -34
package/README.md
CHANGED
|
@@ -39,12 +39,63 @@ shilo push -m "mi mensaje" # con mensaje de commit
|
|
|
39
39
|
shilo push --dry-run # muestra qué haría sin subir
|
|
40
40
|
shilo push --card ABC-123 # agrega "[card: ABC-123]" al mensaje del commit
|
|
41
41
|
shilo push --yes # sin pedir confirmación
|
|
42
|
+
shilo push --verbose # lista archivo por archivo en vez de resumir
|
|
43
|
+
shilo push --concurrency 4 # requests en paralelo (1-32, default 8)
|
|
42
44
|
shilo push ./otra-carpeta --project <uuid> # sin init, proyecto explícito
|
|
43
45
|
```
|
|
44
46
|
|
|
45
47
|
Sólo se suben archivos de **texto**; binarios, oversize (>25 MB) y nombres con
|
|
46
48
|
caracteres no permitidos se saltan con aviso. Los archivos sin cambios no se
|
|
47
|
-
re-suben.
|
|
49
|
+
re-suben.
|
|
50
|
+
|
|
51
|
+
Los nombres aceptan **espacios, acentos y Unicode** (`Notas Metrónomo.txt`, `日本語.md`):
|
|
52
|
+
sólo se rechaza lo que rompe una ruta o no es portable — `/ \ : * ? " < > |`, bytes de
|
|
53
|
+
control, `.`/`..`, nombres de dispositivo de Windows (`CON`, `NUL`, `COM1`…) y el punto
|
|
54
|
+
final. Los nombres se normalizan a NFC, igual que el backend, para que el mismo archivo
|
|
55
|
+
subido desde macOS y desde Windows sea uno solo.
|
|
56
|
+
|
|
57
|
+
La subida va **por lotes** (hasta 100 archivos o 4 MB por request) y **cada lote se
|
|
58
|
+
commitea apenas termina**: si el push se corta a la mitad, lo ya escrito queda en el
|
|
59
|
+
historial en vez de quedar suelto en storage. Contra un servidor viejo que no conoce el
|
|
60
|
+
endpoint de lotes, el CLI avisa y sigue archivo por archivo.
|
|
61
|
+
|
|
62
|
+
#### Qué se ignora
|
|
63
|
+
|
|
64
|
+
`push` respeta **`.gitignore` y `.shiloignore`**, incluidos los de subcarpetas (una
|
|
65
|
+
regla de `src/.gitignore` sólo alcanza a `src/`). `.shiloignore` se lee último, así que
|
|
66
|
+
podés re-incluir con `!` algo que git ignora. Por defecto ya se saltean `.git/`,
|
|
67
|
+
`node_modules/`, `.shilo/`, `.vercel/`, `.DS_Store` y `Thumbs.db`.
|
|
68
|
+
|
|
69
|
+
También se poda cualquier carpeta que **tenga forma de repo git** (`HEAD` + `objects/`
|
|
70
|
+
+ `refs/`), aunque no se llame `.git`: un `.git-ORIGINAL-BACKUP/` trae todo el
|
|
71
|
+
historial, incluidos secretos de commits ya borrados del árbol de trabajo.
|
|
72
|
+
|
|
73
|
+
> **Limitación conocida:** la negación `!` se evalúa por regla, no por directorio. Como
|
|
74
|
+
> en git, `!` no puede re-incluir un archivo si su carpeta padre ya está excluida
|
|
75
|
+
> (`dist/` + `!dist/keep.txt` no funciona); excluí el contenido en vez de la carpeta
|
|
76
|
+
> (`dist/*` + `!dist/keep.txt`).
|
|
77
|
+
|
|
78
|
+
Lo salteado se reporta **agrupado por motivo** (binario, ilegible, symlink, nombre
|
|
79
|
+
inválido, profundidad) con unos pocos ejemplos; si una carpeta concentra muchos
|
|
80
|
+
descartes, el CLI sugiere la línea de `.shiloignore` que la cubre. Con `--verbose` ves
|
|
81
|
+
la lista completa.
|
|
82
|
+
|
|
83
|
+
#### Escaneo de secretos
|
|
84
|
+
|
|
85
|
+
Antes de tocar la red, `push` revisa nombres sospechosos (`.env`, `id_rsa`,
|
|
86
|
+
`*.pem`, credenciales de service account…) y los primeros 8 KB de cada archivo
|
|
87
|
+
buscando patrones de claves (AWS, JWT de Supabase, tokens de GitHub, claves privadas
|
|
88
|
+
PEM…). Si encuentra algo, **no sube nada** y sale con código 1, indicando archivo y
|
|
89
|
+
línea — nunca el valor detectado. Salidas: agregarlo a `.shiloignore`, sacar el
|
|
90
|
+
secreto, o `--allow-secrets` si es un falso positivo.
|
|
91
|
+
|
|
92
|
+
#### Índice local
|
|
93
|
+
|
|
94
|
+
`push` guarda un índice en `.shilo/index.json` (tamaño + mtime + hash de lo último
|
|
95
|
+
subido). Los archivos que no cambiaron se resuelven sin bajar el contenido remoto,
|
|
96
|
+
que es lo que hace rápido el segundo push de una carpeta grande. Se puede ignorar con
|
|
97
|
+
`--no-index` para recomparar todo contra el servidor. Agregá `.shilo/` a tu
|
|
98
|
+
`.gitignore` (el CLI ya no lo sube).
|
|
48
99
|
|
|
49
100
|
En `--dry-run` (y antes de la confirmación interactiva) cada archivo modificado
|
|
50
101
|
muestra **quién lo tocó por última vez y cuándo**, y se avisa explícitamente si vas
|
|
@@ -137,12 +188,25 @@ con AES-256-CBC y con permisos `0600`.
|
|
|
137
188
|
Se almacena el JWT, el refresh token, la expiración y el email; **no** se guarda la
|
|
138
189
|
contraseña.
|
|
139
190
|
|
|
191
|
+
### Tokens de acceso personal (PAT)
|
|
192
|
+
|
|
193
|
+
Un token pasado como `--token abc123` queda en el historial del shell y en la lista de
|
|
194
|
+
procesos (`ps` lo muestra a cualquier usuario de la máquina). El CLI lo acepta —es la
|
|
195
|
+
forma que se copia desde la web— pero avisa. Alternativas:
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
cat pat.txt | shilo login --token - # lee el token de stdin
|
|
199
|
+
SHILO_TOKEN=<pat> shilo init --project <uuid> # variable de entorno (lo natural en CI)
|
|
200
|
+
```
|
|
201
|
+
|
|
140
202
|
## Variables de entorno
|
|
141
203
|
|
|
142
204
|
| Variable | Efecto |
|
|
143
205
|
| ----------------------- | ------------------------------------------------------------- |
|
|
144
206
|
| `SHILO_CONFIG_DIR` | Directorio de config (default `~/.shilo`). |
|
|
145
207
|
| `SHILO_ENCRYPTION_KEY` | Passphrase para cifrar credenciales (recomendado). |
|
|
208
|
+
| `SHILO_TOKEN` | Token de acceso personal (evita pasarlo por `--token`). |
|
|
209
|
+
| `SHILO_CONCURRENCY` | Requests en paralelo del push (1-32, default 8). |
|
|
146
210
|
| `NO_COLOR` | Desactiva colores ANSI. |
|
|
147
211
|
|
|
148
212
|
## Roadmap
|
|
@@ -160,6 +224,13 @@ contraseña.
|
|
|
160
224
|
de contenido + exit code 1), atribución de autor en `push --dry-run`, `pull` que
|
|
161
225
|
resuelve conflictos por archivo en vez de abortar, `lastSync` en `.shilo.json` y
|
|
162
226
|
`push --card <id>` para cruzar commits con un tablero externo (Metrónomo).
|
|
227
|
+
- **Fase 6 — robustez del push** *(hecho)*: `.gitignore` (incluidos los anidados),
|
|
228
|
+
poda de repos git por forma, escaneo de secretos con corte del push, preflight
|
|
229
|
+
100 % offline antes de tocar la red, planificación con un recorrido del árbol
|
|
230
|
+
remoto (una request por carpeta en vez de una por archivo), índice local
|
|
231
|
+
`.shilo/index.json`, lectura perezosa del contenido, reintentos con backoff ante
|
|
232
|
+
429/5xx, concurrencia adaptativa, barra de progreso y confirmación real en
|
|
233
|
+
`init --push`.
|
|
163
234
|
|
|
164
235
|
## Exit codes
|
|
165
236
|
|
|
@@ -168,4 +239,5 @@ contraseña.
|
|
|
168
239
|
| `status` | 2 | El remoto tiene cambios que no tenés. |
|
|
169
240
|
| `diff` | 1 | Hay diferencias entre local y remoto. |
|
|
170
241
|
| `pull` | 1 | Quedaron conflictos sin bajar (o fallas). |
|
|
242
|
+
| `push` | 1 | Se detectaron posibles secretos (nada subido). |
|
|
171
243
|
| cualquiera | 1 | Error. |
|
package/bin/shilo.js
CHANGED
|
@@ -28,13 +28,15 @@ ${ui.c.bold('Opciones de login:')}
|
|
|
28
28
|
--email <e> Email (si se omite, se pregunta).
|
|
29
29
|
--password <p> Contraseña (si se omite, se pregunta oculta).
|
|
30
30
|
--token <pat> Token de acceso personal (en vez de email + contraseña).
|
|
31
|
+
--token - Lee el token de stdin (no queda en el historial del shell).
|
|
31
32
|
--base-url <url> Servidor Shilo (default: https://www.shilo.city).
|
|
33
|
+
${ui.c.dim('También se lee de la variable de entorno SHILO_TOKEN.')}
|
|
32
34
|
|
|
33
35
|
${ui.c.bold('Opciones de init:')}
|
|
34
36
|
--project <uuid> Id del proyecto de Shilo (requerido).
|
|
35
|
-
--token <pat> Token de acceso personal (evita el "login" previo).
|
|
36
|
-
--push Sube la carpeta apenas queda atada.
|
|
37
|
-
--pull Baja la carpeta apenas queda atada.
|
|
37
|
+
--token <pat> Token de acceso personal (evita el "login" previo; o SHILO_TOKEN).
|
|
38
|
+
--push Sube la carpeta apenas queda atada (pregunta, salvo --yes).
|
|
39
|
+
--pull Baja la carpeta apenas queda atada (pregunta, salvo --yes).
|
|
38
40
|
--base-url <url> Servidor Shilo.
|
|
39
41
|
--force No verifica acceso ni pregunta antes de sobrescribir.
|
|
40
42
|
|
|
@@ -51,7 +53,12 @@ ${ui.c.bold('Opciones de push:')}
|
|
|
51
53
|
--card <id> Id de tarjeta/tablero; se agrega al mensaje como [card: id].
|
|
52
54
|
--dry-run Muestra qué haría (y quién tocó cada archivo), sin subir nada.
|
|
53
55
|
--yes No pide confirmación.
|
|
56
|
+
--allow-secrets Sube igual los archivos donde se detectaron posibles secretos.
|
|
57
|
+
--concurrency <n> Requests en paralelo (1-32, default 8; o SHILO_CONCURRENCY).
|
|
58
|
+
--no-index Ignora el índice local (.shilo/index.json) y recompara todo.
|
|
59
|
+
--verbose Lista archivo por archivo en vez de resumir.
|
|
54
60
|
--base-url <url> Servidor Shilo.
|
|
61
|
+
${ui.c.dim('Respeta .gitignore y .shiloignore, incluidos los de subcarpetas.')}
|
|
55
62
|
|
|
56
63
|
${ui.c.bold('Opciones de pull:')}
|
|
57
64
|
--project <uuid> Proyecto origen (pisa el .shilo.json de la carpeta).
|
|
@@ -97,7 +104,7 @@ async function main() {
|
|
|
97
104
|
// valor de --push, perdiendo el positional de la carpeta.
|
|
98
105
|
booleans: [
|
|
99
106
|
'help', 'version', 'h', 'v', 'dry-run', 'yes', 'force', 'push', 'pull',
|
|
100
|
-
'json', 'short', 'stat', 'files',
|
|
107
|
+
'json', 'short', 'stat', 'files', 'allow-secrets', 'no-index', 'verbose',
|
|
101
108
|
],
|
|
102
109
|
aliases: { h: 'help', v: 'version', m: 'message' },
|
|
103
110
|
});
|
package/package.json
CHANGED
package/src/ShiloClient.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*
|
|
12
12
|
* Archivos y commits (Fase 2):
|
|
13
13
|
* - listFiles / getFileMeta / readFile
|
|
14
|
-
* - createFile / writeFile / createFolder / ensurePath
|
|
14
|
+
* - createFile / writeFile / writeBatch / createFolder / ensurePath
|
|
15
15
|
* - createCommit
|
|
16
16
|
*
|
|
17
17
|
* Todos los métodos de red toman `credentials` y devuelven `{ ..., credentials }`
|
|
@@ -21,6 +21,36 @@
|
|
|
21
21
|
const DEFAULT_BASE_URL = 'https://www.shilo.city';
|
|
22
22
|
const DEFAULT_TIMEOUT_MS = 45000;
|
|
23
23
|
|
|
24
|
+
// Reintentos: sólo para fallos que pueden desaparecer solos (red, timeout, 429,
|
|
25
|
+
// 5xx). Un 4xx que no sea 429 es un error del cliente —ruta inválida, permisos,
|
|
26
|
+
// body mal formado— y repetirlo sólo gasta tiempo.
|
|
27
|
+
const DEFAULT_RETRIES = 3;
|
|
28
|
+
const RETRY_BASE_MS = 250;
|
|
29
|
+
const RETRY_MAX_MS = 8000;
|
|
30
|
+
|
|
31
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Backoff exponencial con jitter completo. El jitter importa: sin él, N
|
|
35
|
+
* requests que chocan con el mismo 429 reintentan todos en el mismo
|
|
36
|
+
* milisegundo y vuelven a chocar.
|
|
37
|
+
*/
|
|
38
|
+
function backoffDelay(attempt) {
|
|
39
|
+
const ceiling = Math.min(RETRY_BASE_MS * Math.pow(4, attempt), RETRY_MAX_MS);
|
|
40
|
+
return Math.round(ceiling * (0.5 + Math.random() * 0.5));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Lee `Retry-After` (segundos o fecha HTTP). Devuelve ms, o null. */
|
|
44
|
+
function retryAfterMs(res) {
|
|
45
|
+
const raw = res && res.headers && res.headers.get && res.headers.get('retry-after');
|
|
46
|
+
if (!raw) return null;
|
|
47
|
+
const secs = Number(raw);
|
|
48
|
+
if (Number.isFinite(secs)) return Math.max(0, Math.min(secs * 1000, 60000));
|
|
49
|
+
const when = Date.parse(raw);
|
|
50
|
+
if (!Number.isNaN(when)) return Math.max(0, Math.min(when - Date.now(), 60000));
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
|
|
24
54
|
/**
|
|
25
55
|
* El ápex https://shilo.city responde 307 hacia https://www.shilo.city. Como
|
|
26
56
|
* `fetch` sigue redirects pero descarta el header Authorization al cruzar de
|
|
@@ -33,11 +63,15 @@ function normalizeBaseURL(url) {
|
|
|
33
63
|
}
|
|
34
64
|
|
|
35
65
|
class ShiloError extends Error {
|
|
36
|
-
constructor(message, { status = null, body = null } = {}) {
|
|
66
|
+
constructor(message, { status = null, body = null, retryable = false, notSupported = false } = {}) {
|
|
37
67
|
super(message);
|
|
38
68
|
this.name = 'ShiloError';
|
|
39
69
|
this.status = status;
|
|
40
70
|
this.body = body;
|
|
71
|
+
this.retryable = retryable;
|
|
72
|
+
// El servidor no conoce el endpoint (deploy viejo): el caller puede usar
|
|
73
|
+
// el camino alternativo en vez de abortar.
|
|
74
|
+
this.notSupported = notSupported;
|
|
41
75
|
}
|
|
42
76
|
}
|
|
43
77
|
|
|
@@ -48,9 +82,14 @@ class ShiloClient {
|
|
|
48
82
|
// Opcionales: habilitan refresh de token sin re-login.
|
|
49
83
|
this.supabaseUrl = options.supabaseUrl || null;
|
|
50
84
|
this.supabaseAnonKey = options.supabaseAnonKey || null;
|
|
85
|
+
this.retries = options.retries != null ? Number(options.retries) : DEFAULT_RETRIES;
|
|
86
|
+
// Se avisa de cada 429 para que el caller pueda bajar la concurrencia en vez
|
|
87
|
+
// de seguir martillando al servidor con la misma cadencia.
|
|
88
|
+
this.onThrottle = typeof options.onThrottle === 'function' ? options.onThrottle : null;
|
|
51
89
|
}
|
|
52
90
|
|
|
53
|
-
|
|
91
|
+
/** Un intento único, sin reintentos. */
|
|
92
|
+
async _fetchOnce(url, { method = 'GET', headers = {}, body = null } = {}) {
|
|
54
93
|
const controller = new AbortController();
|
|
55
94
|
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
56
95
|
let res;
|
|
@@ -64,9 +103,11 @@ class ShiloClient {
|
|
|
64
103
|
} catch (err) {
|
|
65
104
|
clearTimeout(timer);
|
|
66
105
|
if (err.name === 'AbortError') {
|
|
67
|
-
throw new ShiloError(`Timeout tras ${this.timeoutMs}ms: ${method} ${url}
|
|
106
|
+
throw new ShiloError(`Timeout tras ${this.timeoutMs}ms: ${method} ${url}`, {
|
|
107
|
+
retryable: true,
|
|
108
|
+
});
|
|
68
109
|
}
|
|
69
|
-
throw new ShiloError(`Fallo de red: ${err.message}
|
|
110
|
+
throw new ShiloError(`Fallo de red: ${err.message}`, { retryable: true });
|
|
70
111
|
}
|
|
71
112
|
clearTimeout(timer);
|
|
72
113
|
|
|
@@ -82,6 +123,43 @@ class ShiloClient {
|
|
|
82
123
|
return { res, data };
|
|
83
124
|
}
|
|
84
125
|
|
|
126
|
+
/**
|
|
127
|
+
* `_fetchOnce` + reintentos con backoff exponencial y jitter. Un 502
|
|
128
|
+
* transitorio o un microcorte de red dejaban de forma permanente ese archivo
|
|
129
|
+
* afuera del push —y si pasaba en la fase de commit, quedaban archivos
|
|
130
|
+
* escritos sin commit que los registrara.
|
|
131
|
+
*/
|
|
132
|
+
async _fetch(url, options = {}) {
|
|
133
|
+
const maxAttempts = Math.max(1, this.retries);
|
|
134
|
+
let nextDelay = 0;
|
|
135
|
+
|
|
136
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
137
|
+
const isLast = attempt === maxAttempts - 1;
|
|
138
|
+
if (attempt > 0) await sleep(nextDelay);
|
|
139
|
+
|
|
140
|
+
let result;
|
|
141
|
+
try {
|
|
142
|
+
result = await this._fetchOnce(url, options);
|
|
143
|
+
} catch (err) {
|
|
144
|
+
if (!err.retryable || isLast) throw err;
|
|
145
|
+
nextDelay = backoffDelay(attempt);
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const { res } = result;
|
|
150
|
+
const throttled = res.status === 429;
|
|
151
|
+
if (throttled && this.onThrottle) this.onThrottle();
|
|
152
|
+
|
|
153
|
+
if ((!throttled && res.status < 500) || isLast) return result;
|
|
154
|
+
|
|
155
|
+
const hinted = retryAfterMs(res);
|
|
156
|
+
nextDelay = hinted != null ? hinted : backoffDelay(attempt);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/* istanbul ignore next — el bucle siempre sale por return o throw */
|
|
160
|
+
throw new ShiloError(`No se pudo completar la petición a ${url}`);
|
|
161
|
+
}
|
|
162
|
+
|
|
85
163
|
_errMessage(data, fallback) {
|
|
86
164
|
if (data && typeof data === 'object') {
|
|
87
165
|
if (typeof data.error === 'string') return data.error;
|
|
@@ -343,6 +421,44 @@ class ShiloClient {
|
|
|
343
421
|
return { file, credentials: creds };
|
|
344
422
|
}
|
|
345
423
|
|
|
424
|
+
/**
|
|
425
|
+
* Escribe varios archivos en una sola llamada (`POST /api/files/:id/batch`).
|
|
426
|
+
* Crea las carpetas intermedias por su cuenta, así que no hace falta un
|
|
427
|
+
* `ensurePath` previo.
|
|
428
|
+
*
|
|
429
|
+
* `files`: [{ path, content, mimeType? }] — máximo 100 y 5 MB por lote.
|
|
430
|
+
*
|
|
431
|
+
* El endpoint responde 200 aunque algunos archivos fallen: el resultado viene
|
|
432
|
+
* POR ARCHIVO, en el mismo orden que se mandó. Los `ok:true` traen
|
|
433
|
+
* `file.previous_storage_path`, que es lo que después pide el commit.
|
|
434
|
+
* Devuelve `{ results, summary, credentials }`.
|
|
435
|
+
*
|
|
436
|
+
* Un servidor viejo (sin el endpoint) responde 404/405: se marca en el error
|
|
437
|
+
* con `notSupported` para que el caller pueda caer al camino de a un archivo.
|
|
438
|
+
*/
|
|
439
|
+
async writeBatch(projectId, files, credentials) {
|
|
440
|
+
const url = `${this.baseURL}/api/files/${projectId}/batch`;
|
|
441
|
+
const { res, data, credentials: creds } = await this._authFetch(url, credentials, {
|
|
442
|
+
method: 'POST',
|
|
443
|
+
body: { files },
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
if (!res.ok) {
|
|
447
|
+
throw new ShiloError(
|
|
448
|
+
`No se pudo escribir el lote: ${this._errMessage(data, `HTTP ${res.status}`)}`,
|
|
449
|
+
{ status: res.status, body: data, notSupported: res.status === 404 || res.status === 405 }
|
|
450
|
+
);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
const results = data && Array.isArray(data.results) ? data.results : [];
|
|
454
|
+
const summary = (data && data.summary) || {
|
|
455
|
+
total: results.length,
|
|
456
|
+
succeeded: results.filter((r) => r && r.ok).length,
|
|
457
|
+
failed: results.filter((r) => r && !r.ok).length,
|
|
458
|
+
};
|
|
459
|
+
return { results, summary, credentials: creds };
|
|
460
|
+
}
|
|
461
|
+
|
|
346
462
|
/**
|
|
347
463
|
* Crea una carpeta bajo `parentPath` (vacío = raíz). Un 409 significa que ya
|
|
348
464
|
* existe: no es fatal, devolvemos `{ folder, existed: true }`.
|
package/src/args.js
CHANGED
|
@@ -42,7 +42,8 @@ function parseArgs(argv, { booleans = [], aliases = {} } = {}) {
|
|
|
42
42
|
out[key] = true;
|
|
43
43
|
} else {
|
|
44
44
|
const next = argv[i + 1];
|
|
45
|
-
|
|
45
|
+
// `-` a secas es un valor legítimo ("leer de stdin"), no otra bandera.
|
|
46
|
+
if (next !== undefined && (next === '-' || !next.startsWith('-'))) {
|
|
46
47
|
out[key] = next;
|
|
47
48
|
i++;
|
|
48
49
|
} else {
|
|
@@ -58,7 +59,8 @@ function parseArgs(argv, { booleans = [], aliases = {} } = {}) {
|
|
|
58
59
|
out[key] = true;
|
|
59
60
|
} else {
|
|
60
61
|
const next = argv[i + 1];
|
|
61
|
-
|
|
62
|
+
// `-` a secas es un valor legítimo ("leer de stdin"), no otra bandera.
|
|
63
|
+
if (next !== undefined && (next === '-' || !next.startsWith('-'))) {
|
|
62
64
|
out[key] = next;
|
|
63
65
|
i++;
|
|
64
66
|
} else {
|
package/src/commands/init.js
CHANGED
|
@@ -5,6 +5,7 @@ const fs = require('fs');
|
|
|
5
5
|
const config = require('../config');
|
|
6
6
|
const remote = require('../remote');
|
|
7
7
|
const prompt = require('../prompt');
|
|
8
|
+
const tokens = require('../token');
|
|
8
9
|
const ui = require('../ui');
|
|
9
10
|
const { ShiloClient } = require('../ShiloClient');
|
|
10
11
|
|
|
@@ -43,8 +44,16 @@ async function init(args) {
|
|
|
43
44
|
|
|
44
45
|
// --token: persistir credenciales antes del chequeo de sesión, para que el
|
|
45
46
|
// comando de una línea copiado desde la web ande sin "shilo login" previo.
|
|
46
|
-
|
|
47
|
+
let token = '';
|
|
48
|
+
let tokenSource = null;
|
|
49
|
+
try {
|
|
50
|
+
({ token, source: tokenSource } = await tokens.resolveToken(args));
|
|
51
|
+
} catch (e) {
|
|
52
|
+
ui.error(e.message);
|
|
53
|
+
return 1;
|
|
54
|
+
}
|
|
47
55
|
if (token) {
|
|
56
|
+
tokens.warnIfExposed(tokenSource, ui);
|
|
48
57
|
const existing = config.load() || {};
|
|
49
58
|
config.save({
|
|
50
59
|
baseURL: args['base-url'] || existing.baseURL || DEFAULT_BASE_URL,
|
|
@@ -98,14 +107,16 @@ async function init(args) {
|
|
|
98
107
|
ui.success(`Carpeta atada al proyecto ${ui.c.bold(projectId)}.`);
|
|
99
108
|
ui.info(ui.c.dim(`Remote guardado en ${written}`));
|
|
100
109
|
|
|
110
|
+
// --push/--pull no implican confirmación: el usuario pidió *encadenar* el
|
|
111
|
+
// comando, no saltearse su preflight. Sin --yes, push muestra el plan (y el
|
|
112
|
+
// resultado del escaneo de secretos) y pregunta como si se hubiera invocado
|
|
113
|
+
// a mano. `args.yes` viaja tal cual en el spread.
|
|
101
114
|
if (args.push) {
|
|
102
|
-
|
|
103
|
-
return require('./push')({ ...args, _: [dir], yes: true });
|
|
115
|
+
return require('./push')({ ...args, _: [dir] });
|
|
104
116
|
}
|
|
105
117
|
|
|
106
118
|
if (args.pull) {
|
|
107
|
-
|
|
108
|
-
return require('./pull')({ ...args, _: [dir], yes: true });
|
|
119
|
+
return require('./pull')({ ...args, _: [dir] });
|
|
109
120
|
}
|
|
110
121
|
|
|
111
122
|
ui.info(`Ahora podés subir con: ${ui.c.bold('shilo push')}`);
|
package/src/commands/login.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const config = require('../config');
|
|
4
4
|
const prompt = require('../prompt');
|
|
5
|
+
const tokens = require('../token');
|
|
5
6
|
const ui = require('../ui');
|
|
6
7
|
const { ShiloClient } = require('../ShiloClient');
|
|
7
8
|
|
|
@@ -17,8 +18,18 @@ const DEFAULT_BASE_URL = 'https://www.shilo.city';
|
|
|
17
18
|
async function login(args) {
|
|
18
19
|
const baseURL = args['base-url'] || (config.load() || {}).baseURL || DEFAULT_BASE_URL;
|
|
19
20
|
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
let token = '';
|
|
22
|
+
let source = null;
|
|
23
|
+
try {
|
|
24
|
+
({ token, source } = await tokens.resolveToken(args));
|
|
25
|
+
} catch (e) {
|
|
26
|
+
ui.error(e.message);
|
|
27
|
+
return 1;
|
|
28
|
+
}
|
|
29
|
+
if (token) {
|
|
30
|
+
tokens.warnIfExposed(source, ui);
|
|
31
|
+
return loginWithToken(token, baseURL);
|
|
32
|
+
}
|
|
22
33
|
|
|
23
34
|
let email = args.email;
|
|
24
35
|
let pass = args.password;
|