shilo-cli 0.8.0 → 0.10.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 +57 -0
- package/bin/shilo.js +30 -2
- package/package.json +4 -3
- package/src/ShiloClient.js +45 -6
- package/src/checks.js +57 -0
- package/src/commands/deploys.js +105 -0
- package/src/commands/diff.js +1 -1
- package/src/commands/init.js +8 -0
- package/src/commands/push.js +436 -62
- package/src/commands/status.js +112 -14
- package/src/commands/sync.js +240 -0
- package/src/folders.js +141 -0
- package/src/projectconfig.js +76 -0
- package/src/remote.js +3 -0
- package/src/safelist.js +101 -0
- package/src/secrets.js +75 -9
- package/src/sync.js +52 -4
package/README.md
CHANGED
|
@@ -36,6 +36,7 @@ cd mi-carpeta
|
|
|
36
36
|
shilo init --project <uuid> # ata la carpeta al repo (escribe .shilo.json)
|
|
37
37
|
shilo push # sube: crea/actualiza + commit
|
|
38
38
|
shilo push -m "mi mensaje" # con mensaje de commit
|
|
39
|
+
shilo push --deploy prod # este push despliega a producción ("[deploy: prod]")
|
|
39
40
|
shilo push --dry-run # muestra qué haría sin subir
|
|
40
41
|
shilo push --card ABC-123 # agrega "[card: ABC-123]" al mensaje del commit
|
|
41
42
|
shilo push --agent claude-code --model claude-opus-5 # lo escribió un agente, y queda anotado
|
|
@@ -169,6 +170,57 @@ shilo log --json
|
|
|
169
170
|
|
|
170
171
|
La paginación es **por commit**, no por archivo: si un archivo no aparece, subí `-n`.
|
|
171
172
|
|
|
173
|
+
### Deploys a Vercel (deploys)
|
|
174
|
+
|
|
175
|
+
Si el owner conectó el proyecto con Vercel (pestaña **Deploys** en la web), cada
|
|
176
|
+
push que cambia algo dispara un deploy: por defecto a **preview**. Producción sólo
|
|
177
|
+
si se pide explícitamente.
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
shilo push --deploy prod # este push va a producción
|
|
181
|
+
shilo push --deploy off # este push no despliega
|
|
182
|
+
shilo deploys # destino y últimos deploys, con estado y URL
|
|
183
|
+
shilo deploys -n 20 --json
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
`--deploy` sólo agrega el trailer `[deploy: …]` al mensaje; escribirlo a mano en
|
|
187
|
+
`-m` da lo mismo.
|
|
188
|
+
|
|
189
|
+
### Respaldar todas las carpetas (sync)
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
shilo sync # lista las carpetas conectadas y su último sync
|
|
193
|
+
shilo sync --all # sube todas, una tras otra
|
|
194
|
+
shilo sync ./boda # sólo esa carpeta
|
|
195
|
+
shilo sync a4c2 # o por id de proyecto (alcanza un prefijo)
|
|
196
|
+
shilo sync --scan ~/proyectos # registra las carpetas ya conectadas que haya ahí
|
|
197
|
+
shilo sync --forget ./vieja # la saca del registro (no toca el disco)
|
|
198
|
+
shilo sync --all --dry-run # qué subiría cada una, sin subir nada
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
El CLI lleva un registro de carpetas conectadas en `~/.shilo/carpetas.json`. Se llena
|
|
202
|
+
solo: cada `init`, `push` o `pull` registra su carpeta. Las que se conectaron antes
|
|
203
|
+
de esta versión se suman una vez con `--scan`, que busca `.shilo.json` hasta 6 niveles
|
|
204
|
+
abajo (no entra en `node_modules`, `.git` ni `dist`).
|
|
205
|
+
|
|
206
|
+
Sincronizar es **subir**: cada carpeta pasa por `shilo push --yes`, con sus mismas
|
|
207
|
+
garantías (un conflicto queda como propuesta abierta, una carpeta con posibles
|
|
208
|
+
secretos no sube nada). Cada push corre en su propio proceso, así que si una carpeta
|
|
209
|
+
falla las demás siguen, y al final hay un resumen. **Un respaldo no despliega:**
|
|
210
|
+
manda `--deploy off` salvo que pases otro `--deploy`.
|
|
211
|
+
|
|
212
|
+
**Una carpeta sin `base` se saltea.** `base` es la versión del proyecto sobre la que
|
|
213
|
+
está tu copia; la escriben `push` y `pull`. Sin ella, un push no puede saber si tu
|
|
214
|
+
copia está vieja y subiría tus archivos como cambios, pisando lo más nuevo. Esas
|
|
215
|
+
carpetas aparecen como "requiere atención": revisalas con `shilo status` y
|
|
216
|
+
`shilo diff`, subilas a mano una vez con `shilo push` (o bajá lo nuevo con
|
|
217
|
+
`shilo pull`) y desde ahí entran solas al respaldo.
|
|
218
|
+
|
|
219
|
+
Exit code 1 = alguna carpeta falló o se salteó. Una propuesta que queda esperando
|
|
220
|
+
aprobación (exit 3 de `push`) cuenta como respaldada. El plan de las etapas
|
|
221
|
+
siguientes (respaldo programado, vista en la web) está en `PLAN_SINCRONIZAR.md` en la
|
|
222
|
+
raíz del repo.
|
|
223
|
+
|
|
172
224
|
### Diferencias (diff)
|
|
173
225
|
|
|
174
226
|
```bash
|
|
@@ -307,6 +359,10 @@ SHILO_TOKEN=<pat> shilo init --project <uuid> # variable de entorno (lo natura
|
|
|
307
359
|
manda una propuesta con la base exacta; el servidor la combina con la versión actual
|
|
308
360
|
y nunca pisa un archivo que otra persona cambió. `base` en `.shilo.json`,
|
|
309
361
|
`registerBlob` para binarios, camino viejo sólo como reserva.
|
|
362
|
+
- **Fase 8 — deploys** *(hecho)*: `push --deploy <prod|preview|off>` y
|
|
363
|
+
`shilo deploys` para el deploy automático a Vercel (PLAN_DEPLOY_VERCEL).
|
|
364
|
+
- **Fase 9 — respaldo** *(Etapa 1 hecha)*: registro de carpetas conectadas y
|
|
365
|
+
`shilo sync [--all]`. Lo que sigue, en `PLAN_SINCRONIZAR.md`.
|
|
310
366
|
|
|
311
367
|
## Exit codes
|
|
312
368
|
|
|
@@ -316,4 +372,5 @@ SHILO_TOKEN=<pat> shilo init --project <uuid> # variable de entorno (lo natura
|
|
|
316
372
|
| `diff` | 1 | Hay diferencias entre local y remoto. |
|
|
317
373
|
| `pull` | 1 | Quedaron conflictos sin bajar (o fallas). |
|
|
318
374
|
| `push` | 1 | Se detectaron posibles secretos (nada subido). |
|
|
375
|
+
| `sync` | 1 | Alguna carpeta no se respaldó (falló o se salteó). |
|
|
319
376
|
| cualquiera | 1 | Error. |
|
package/bin/shilo.js
CHANGED
|
@@ -24,7 +24,9 @@ ${ui.c.bold('Comandos:')}
|
|
|
24
24
|
diff [archivo] Diferencias entre tu copia local y la remota.
|
|
25
25
|
review [versión] Quién revisó una versión, o dejar tu revisión.
|
|
26
26
|
export <carpeta> Escribe la historia del proyecto como un repositorio Git.
|
|
27
|
-
|
|
27
|
+
deploys Destino e historial de deploys a Vercel del proyecto.
|
|
28
|
+
sync [carpeta|uuid] Respalda las carpetas conectadas (--all: todas).
|
|
29
|
+
help Muestra esta ayuda.
|
|
28
30
|
|
|
29
31
|
${ui.c.bold('Opciones de login:')}
|
|
30
32
|
--email <e> Email (si se omite, se pregunta).
|
|
@@ -55,6 +57,7 @@ ${ui.c.bold('Opciones de push:')}
|
|
|
55
57
|
-m, --message <m> Mensaje del commit.
|
|
56
58
|
--only <ruta> Sube sólo esa ruta (archivo o carpeta). Repetible.
|
|
57
59
|
--card <id> Id de tarjeta/tablero; se agrega al mensaje como [card: id].
|
|
60
|
+
--deploy <d> prod | preview | off: pisa el deploy automático de este push.
|
|
58
61
|
--agent <nombre> Declara que lo escribió un agente (ej: claude-code).
|
|
59
62
|
--model <modelo> Qué modelo, junto con --agent.
|
|
60
63
|
--session <id> Id de la conversación, para agrupar guardados.
|
|
@@ -62,12 +65,17 @@ ${ui.c.bold('Opciones de push:')}
|
|
|
62
65
|
--prompt-file <f> Igual, leyendo el pedido de un archivo (máx. 64 KB).
|
|
63
66
|
--dry-run Muestra qué haría (y quién tocó cada archivo), sin subir nada.
|
|
64
67
|
--yes No pide confirmación.
|
|
65
|
-
--allow-secrets Sube igual
|
|
68
|
+
--allow-secrets Sube igual TODO lo que parezca un secreto (avanzado). Mejor:
|
|
69
|
+
responder en la terminal; la decisión queda en .shilo/.
|
|
66
70
|
--concurrency <n> Requests en paralelo (1-32, default 8; o SHILO_CONCURRENCY).
|
|
67
71
|
--no-index Ignora el índice local (.shilo/index.json) y recompara todo.
|
|
72
|
+
--allow-failing-checks
|
|
73
|
+
Si falla un chequeo de shilo.config.json, propone igual
|
|
74
|
+
(queda marcada como fallida y aplicarla exige forzar).
|
|
68
75
|
--verbose Lista archivo por archivo en vez de resumir.
|
|
69
76
|
--base-url <url> Servidor Shilo.
|
|
70
77
|
${ui.c.dim('Respeta .gitignore y .shiloignore, incluidos los de subcarpetas.')}
|
|
78
|
+
${ui.c.dim('Exit code 3 = todo guardado, pero espera aprobación (no es un error).')}
|
|
71
79
|
|
|
72
80
|
${ui.c.bold('Opciones de pull:')}
|
|
73
81
|
--project <uuid> Proyecto origen (pisa el .shilo.json de la carpeta).
|
|
@@ -105,6 +113,23 @@ ${ui.c.bold('Opciones de export:')}
|
|
|
105
113
|
--base-url <url> Servidor Shilo.
|
|
106
114
|
${ui.c.dim('La primera vez, a una carpeta vacía; después sólo trae lo nuevo. Público sin sesión.')}
|
|
107
115
|
|
|
116
|
+
${ui.c.bold('Opciones de deploys:')}
|
|
117
|
+
-n <N> Cuántos deploys mostrar (1-20, default 10).
|
|
118
|
+
--json Salida JSON.
|
|
119
|
+
--project <uuid> Proyecto (pisa el .shilo.json de la carpeta).
|
|
120
|
+
|
|
121
|
+
${ui.c.bold('Opciones de sync:')}
|
|
122
|
+
(sin argumentos) Lista las carpetas conectadas y su último sync.
|
|
123
|
+
--all Sube todas las carpetas registradas, una tras otra.
|
|
124
|
+
--scan [raíz] Registra las carpetas con .shilo.json bajo raíz (default: la actual).
|
|
125
|
+
--forget <carpeta> La saca del registro (no toca nada en disco).
|
|
126
|
+
-m, --message <m> Mensaje de los commits (default: "Respaldo (shilo sync) <fecha>").
|
|
127
|
+
--deploy <d> prod | preview | off (default off: un respaldo no despliega).
|
|
128
|
+
--dry-run Muestra qué subiría cada carpeta, sin subir nada.
|
|
129
|
+
${ui.c.dim('Se registran solas con init, push o pull. Una carpeta sin "base" en su')}
|
|
130
|
+
${ui.c.dim('.shilo.json se saltea: subila a mano una vez con "shilo push".')}
|
|
131
|
+
${ui.c.dim('Exit code 1 = alguna carpeta falló o se salteó.')}
|
|
132
|
+
|
|
108
133
|
${ui.c.bold('Globales:')}
|
|
109
134
|
-v, --version Versión del CLI.
|
|
110
135
|
-h, --help Ayuda.
|
|
@@ -122,6 +147,8 @@ const COMMANDS = {
|
|
|
122
147
|
diff: () => require('../src/commands/diff'),
|
|
123
148
|
review: () => require('../src/commands/review'),
|
|
124
149
|
export: () => require('../src/commands/export'),
|
|
150
|
+
deploys: () => require('../src/commands/deploys'),
|
|
151
|
+
sync: () => require('../src/commands/sync'),
|
|
125
152
|
};
|
|
126
153
|
|
|
127
154
|
async function main() {
|
|
@@ -133,6 +160,7 @@ async function main() {
|
|
|
133
160
|
booleans: [
|
|
134
161
|
'help', 'version', 'h', 'v', 'dry-run', 'yes', 'force', 'push', 'pull',
|
|
135
162
|
'json', 'short', 'stat', 'files', 'allow-secrets', 'no-index', 'verbose',
|
|
163
|
+
'allow-failing-checks', 'all',
|
|
136
164
|
'show-ignored', 'include-ignored', 'aprobado', 'reparos', 'rechazado',
|
|
137
165
|
],
|
|
138
166
|
aliases: { h: 'help', v: 'version', m: 'message' },
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shilo-cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "CLI para sincronizar carpetas locales con repositorios de Shilo: push, pull, status, log y
|
|
3
|
+
"version": "0.10.0",
|
|
4
|
+
"description": "CLI para sincronizar carpetas locales con repositorios de Shilo: push, pull, status, log, diff y sync.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"shilo",
|
|
7
7
|
"cli",
|
|
@@ -25,7 +25,8 @@
|
|
|
25
25
|
"README.md"
|
|
26
26
|
],
|
|
27
27
|
"scripts": {
|
|
28
|
-
"start": "node bin/shilo.js"
|
|
28
|
+
"start": "node bin/shilo.js",
|
|
29
|
+
"test": "node --test"
|
|
29
30
|
},
|
|
30
31
|
"engines": {
|
|
31
32
|
"node": ">=18"
|
package/src/ShiloClient.js
CHANGED
|
@@ -791,6 +791,29 @@ class ShiloClient {
|
|
|
791
791
|
};
|
|
792
792
|
}
|
|
793
793
|
|
|
794
|
+
/**
|
|
795
|
+
* Destino e historial de deploys a Vercel (PLAN_DEPLOY_VERCEL). Devuelve
|
|
796
|
+
* `{ connected, can_manage, target, deployments, credentials }`. Un servidor
|
|
797
|
+
* sin deploys responde 404: se marca `notSupported`.
|
|
798
|
+
*/
|
|
799
|
+
async getDeploys(projectId, credentials) {
|
|
800
|
+
const url = `${this.baseURL}/api/deploys/${encodeURIComponent(projectId)}`;
|
|
801
|
+
const { res, data, credentials: creds } = await this._authFetch(url, credentials);
|
|
802
|
+
if (!res.ok) {
|
|
803
|
+
throw new ShiloError(
|
|
804
|
+
`No se pudieron leer los deploys: ${this._errMessage(data, `HTTP ${res.status}`)}`,
|
|
805
|
+
{ status: res.status, body: data, notSupported: res.status === 404 || res.status === 405 }
|
|
806
|
+
);
|
|
807
|
+
}
|
|
808
|
+
return {
|
|
809
|
+
connected: !!(data && data.connected),
|
|
810
|
+
can_manage: !!(data && data.can_manage),
|
|
811
|
+
target: (data && data.target) || null,
|
|
812
|
+
deployments: data && Array.isArray(data.deployments) ? data.deployments : [],
|
|
813
|
+
credentials: creds,
|
|
814
|
+
};
|
|
815
|
+
}
|
|
816
|
+
|
|
794
817
|
/**
|
|
795
818
|
* Propuestas (Etapa 3). Guardar es proponer: el cliente dice de qué versión
|
|
796
819
|
* partió (`base`, el oid de la versión actual que vio) y qué cambió, y el
|
|
@@ -818,6 +841,9 @@ class ShiloClient {
|
|
|
818
841
|
return {
|
|
819
842
|
main: (data && data.main) || null,
|
|
820
843
|
proposals: data && Array.isArray(data.proposals) ? data.proposals : [],
|
|
844
|
+
// Servidores anteriores no los mandan: quien llama tiene que tolerar null.
|
|
845
|
+
viewer: (data && data.viewer) || null,
|
|
846
|
+
project: (data && data.project) || null,
|
|
821
847
|
credentials: creds,
|
|
822
848
|
};
|
|
823
849
|
}
|
|
@@ -836,11 +862,16 @@ class ShiloClient {
|
|
|
836
862
|
* `[card: …]` del mensaje, que se queda porque es el formato de todo lo ya
|
|
837
863
|
* guardado; la clave es la que se puede consultar.
|
|
838
864
|
*
|
|
839
|
-
*
|
|
840
|
-
*
|
|
865
|
+
* `chequeos` es el resultado de los chequeos del proyecto (MU-93,
|
|
866
|
+
* `checks.js`): la propuesta lo lleva y una fallida no se aplica sola.
|
|
867
|
+
*
|
|
868
|
+
* Devuelve `{ applied, empty, proposal, conflicts, blockedBy, lineas,
|
|
869
|
+
* chequeos, piezas, main, message, warnings, credentials }`; `lineas` son las rutas
|
|
870
|
+
* que el servidor combinó por líneas (MU-92) y `piezas`, las piezas USEE
|
|
871
|
+
* que dejarían de pasar el Nivel 1 (MU-94). Los 200 y 202 son respuestas normales (aplicada /
|
|
841
872
|
* quedó abierta); cualquier otro estado es error.
|
|
842
873
|
*/
|
|
843
|
-
async propose(projectId, { base, title, summary, changes, agent, prompt, card }, credentials) {
|
|
874
|
+
async propose(projectId, { base, title, summary, changes, agent, prompt, card, chequeos }, credentials) {
|
|
844
875
|
const url = `${this.baseURL}/api/projects/proposals`;
|
|
845
876
|
const { res, data, credentials: creds } = await this._authFetch(url, credentials, {
|
|
846
877
|
method: 'POST',
|
|
@@ -853,6 +884,7 @@ class ShiloClient {
|
|
|
853
884
|
...(agent ? { agent } : {}),
|
|
854
885
|
...(prompt ? { prompt } : {}),
|
|
855
886
|
...(card ? { card } : {}),
|
|
887
|
+
...(chequeos ? { chequeos } : {}),
|
|
856
888
|
},
|
|
857
889
|
});
|
|
858
890
|
if (!res.ok && res.status !== 202) {
|
|
@@ -867,6 +899,9 @@ class ShiloClient {
|
|
|
867
899
|
proposal: (data && data.proposal) || null,
|
|
868
900
|
conflicts: data && Array.isArray(data.conflicts) ? data.conflicts : [],
|
|
869
901
|
blockedBy: data && Array.isArray(data.blocked_by) ? data.blocked_by : [],
|
|
902
|
+
lineas: data && Array.isArray(data.lineas) ? data.lineas : [],
|
|
903
|
+
chequeos: (data && data.chequeos) || null,
|
|
904
|
+
piezas: data && Array.isArray(data.piezas) ? data.piezas : [],
|
|
870
905
|
main: (data && data.main) || null,
|
|
871
906
|
message: (data && data.message) || '',
|
|
872
907
|
warnings: data && Array.isArray(data.warnings) ? data.warnings : [],
|
|
@@ -876,14 +911,16 @@ class ShiloClient {
|
|
|
876
911
|
|
|
877
912
|
/**
|
|
878
913
|
* Resuelve una propuesta abierta: `apply` (dueño) o `discard` (dueño o
|
|
879
|
-
* autor).
|
|
914
|
+
* autor). `resolutions` ({ ruta: 'actual' | 'propuesta' | 'ambas' }) decide
|
|
915
|
+
* los archivos en conflicto al aplicar; `force` aplica aunque sus chequeos
|
|
916
|
+
* o sus piezas USEE hayan fallado (MU-93, MU-94). Devuelve `{ applied, proposal, conflicts, main, credentials }`; con
|
|
880
917
|
* `apply` un 409 significa que sigue en conflicto y viene con los datos.
|
|
881
918
|
*/
|
|
882
|
-
async resolveProposal(projectId, id, action, credentials) {
|
|
919
|
+
async resolveProposal(projectId, id, action, credentials, { resolutions = null, force = false } = {}) {
|
|
883
920
|
const url = `${this.baseURL}/api/projects/proposals`;
|
|
884
921
|
const { res, data, credentials: creds } = await this._authFetch(url, credentials, {
|
|
885
922
|
method: 'POST',
|
|
886
|
-
body: { projectId, id, action },
|
|
923
|
+
body: { projectId, id, action, ...(resolutions ? { resolutions } : {}), ...(force ? { force: true } : {}) },
|
|
887
924
|
});
|
|
888
925
|
const stillOpen = action === 'apply' && res.status === 409 && data && data.proposal;
|
|
889
926
|
if (!res.ok && !stillOpen) {
|
|
@@ -896,6 +933,8 @@ class ShiloClient {
|
|
|
896
933
|
applied: !!(data && data.applied),
|
|
897
934
|
proposal: (data && data.proposal) || null,
|
|
898
935
|
conflicts: data && Array.isArray(data.conflicts) ? data.conflicts : [],
|
|
936
|
+
lineas: data && Array.isArray(data.lineas) ? data.lineas : [],
|
|
937
|
+
piezas: data && Array.isArray(data.piezas) ? data.piezas : [],
|
|
899
938
|
main: (data && data.main) || null,
|
|
900
939
|
message: (data && data.message) || '',
|
|
901
940
|
credentials: creds,
|
package/src/checks.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chequeos de propuesta (EP-S2, MU-93).
|
|
3
|
+
*
|
|
4
|
+
* Un proyecto declara en `shilo.config.json` los comandos que tienen que pasar
|
|
5
|
+
* antes de proponer (`"chequeos": ["npm test"]`). `shilo push` los corre en la
|
|
6
|
+
* carpeta, uno por uno y en orden, con la salida a la vista, y el resultado
|
|
7
|
+
* viaja con la propuesta: la web la muestra verificada o fallida, y aplicar una
|
|
8
|
+
* fallida exige forzar.
|
|
9
|
+
*
|
|
10
|
+
* Se corta en el primero que falla, como un hook de git: lo que viene después
|
|
11
|
+
* suele depender de lo anterior (instalar, compilar, probar) y correrlo sólo
|
|
12
|
+
* hace más largo el error. El resultado registra lo que se corrió.
|
|
13
|
+
*
|
|
14
|
+
* `run` se inyecta para probarlo sin lanzar procesos.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
'use strict';
|
|
18
|
+
|
|
19
|
+
const childProcess = require('child_process');
|
|
20
|
+
|
|
21
|
+
// Un chequeo colgado no puede dejar colgado el push: a los diez minutos se corta
|
|
22
|
+
// y cuenta como fallido.
|
|
23
|
+
const CHECK_TIMEOUT_MS = 10 * 60 * 1000;
|
|
24
|
+
|
|
25
|
+
function spawnCheck(comando, dir) {
|
|
26
|
+
const r = childProcess.spawnSync(comando, {
|
|
27
|
+
cwd: dir,
|
|
28
|
+
shell: true,
|
|
29
|
+
stdio: 'inherit',
|
|
30
|
+
timeout: CHECK_TIMEOUT_MS,
|
|
31
|
+
windowsHide: true,
|
|
32
|
+
});
|
|
33
|
+
// Sin `status` = no llegó a terminar (no se pudo lanzar, o se cortó por tiempo).
|
|
34
|
+
return { codigo: typeof r.status === 'number' ? r.status : null };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @param {string} dir - la carpeta donde se corren
|
|
39
|
+
* @param {string[]} comandos
|
|
40
|
+
* @param {{ run?: Function, now?: Function, onStart?: Function }} [opts]
|
|
41
|
+
* @returns {{ resultado: 'ok'|'fallido', comandos: Array<{ comando, ok, codigo?, ms }> }}
|
|
42
|
+
*/
|
|
43
|
+
function runChecks(dir, comandos, { run = spawnCheck, now = Date.now, onStart = () => {} } = {}) {
|
|
44
|
+
const out = [];
|
|
45
|
+
for (const comando of comandos) {
|
|
46
|
+
onStart(comando, out.length + 1, comandos.length);
|
|
47
|
+
const t0 = now();
|
|
48
|
+
const { codigo } = run(comando, dir);
|
|
49
|
+
const entry = { comando, ok: codigo === 0, ms: Math.max(0, Math.round(now() - t0)) };
|
|
50
|
+
if (codigo !== null) entry.codigo = codigo;
|
|
51
|
+
out.push(entry);
|
|
52
|
+
if (!entry.ok) break;
|
|
53
|
+
}
|
|
54
|
+
return { resultado: out.every((c) => c.ok) ? 'ok' : 'fallido', comandos: out };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
module.exports = { runChecks, CHECK_TIMEOUT_MS };
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const config = require('../config');
|
|
4
|
+
const ui = require('../ui');
|
|
5
|
+
const sync = require('../sync');
|
|
6
|
+
const { resolveContext } = require('../context');
|
|
7
|
+
|
|
8
|
+
const DEFAULT_LIMIT = 10;
|
|
9
|
+
|
|
10
|
+
const STATUS = {
|
|
11
|
+
queued: (s) => ui.c.dim(s),
|
|
12
|
+
running: (s) => ui.c.cyan(s),
|
|
13
|
+
uploading: (s) => ui.c.cyan(s),
|
|
14
|
+
building: (s) => ui.c.cyan(s),
|
|
15
|
+
ready: (s) => ui.c.green(s),
|
|
16
|
+
error: (s) => ui.c.red(s),
|
|
17
|
+
canceled: (s) => ui.c.dim(s),
|
|
18
|
+
superseded: (s) => ui.c.dim(s),
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const AUTO = {
|
|
22
|
+
off: 'sólo manual',
|
|
23
|
+
preview: 'preview en cada guardado',
|
|
24
|
+
production: 'producción en cada guardado',
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* shilo deploys [-n <N>] [--project <uuid>] [--json]
|
|
29
|
+
*
|
|
30
|
+
* Destino e historial de deploys a Vercel del proyecto (PLAN_DEPLOY_VERCEL,
|
|
31
|
+
* Etapa 4.2). Sólo lectura. El servidor devuelve como mucho los últimos 20.
|
|
32
|
+
*/
|
|
33
|
+
async function deploys(args) {
|
|
34
|
+
const ctx = resolveContext({ ...args, _: [] });
|
|
35
|
+
if (!ctx.ok) return ctx.code;
|
|
36
|
+
|
|
37
|
+
const { projectId, baseURL, cfg, client, mgr } = ctx;
|
|
38
|
+
|
|
39
|
+
const limit = parseLimit(args.n || args.number);
|
|
40
|
+
if (limit === null) {
|
|
41
|
+
ui.error('El valor de -n debe ser un entero entre 1 y 20.');
|
|
42
|
+
return 1;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
let info;
|
|
46
|
+
try {
|
|
47
|
+
info = await client.getDeploys(projectId, await mgr.valid());
|
|
48
|
+
} catch (e) {
|
|
49
|
+
ui.error(e.notSupported ? 'Este servidor de Shilo todavía no tiene deploys.' : e.message);
|
|
50
|
+
return 1;
|
|
51
|
+
} finally {
|
|
52
|
+
config.persistCredsIfChanged(cfg, mgr.current);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const list = info.deployments.slice(0, limit);
|
|
56
|
+
|
|
57
|
+
if (args.json) {
|
|
58
|
+
console.log(JSON.stringify({ projectId, connected: info.connected, target: info.target, deployments: list }, null, 2));
|
|
59
|
+
return 0;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
ui.info(`Deploys del proyecto — ${ui.c.cyan(baseURL)}`);
|
|
63
|
+
if (!info.target) {
|
|
64
|
+
ui.info('');
|
|
65
|
+
ui.info('Este proyecto no despliega a ningún lado. El owner lo configura en la');
|
|
66
|
+
ui.info('pestaña "Deploys" del proyecto en la web.');
|
|
67
|
+
return 0;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const t = info.target;
|
|
71
|
+
ui.info(`Vercel: ${ui.c.bold(t.vercel_project_name || t.vercel_project)} · ${AUTO[t.auto_target] || t.auto_target}`);
|
|
72
|
+
if (!info.connected) {
|
|
73
|
+
ui.warn('La conexión con Vercel no está activa: el owner tiene que volver a conectar.');
|
|
74
|
+
}
|
|
75
|
+
ui.info('');
|
|
76
|
+
|
|
77
|
+
if (list.length === 0) {
|
|
78
|
+
ui.info('Todavía no hubo deploys.');
|
|
79
|
+
return 0;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
for (const d of list) {
|
|
83
|
+
const paint = STATUS[d.status] || ((s) => s);
|
|
84
|
+
const commit = d.commit_oid ? String(d.commit_oid).slice(0, 8) : '--------';
|
|
85
|
+
const target = d.target === 'production' ? ui.c.bold('prod ') : 'preview';
|
|
86
|
+
const card = d.card ? ui.c.dim(` [card: ${d.card}]`) : '';
|
|
87
|
+
ui.info(`${paint(pad(d.status, 10))} ${target} ${ui.c.yellow(commit)} ${ui.c.dim(sync.formatDate(d.created_at))}${card}`);
|
|
88
|
+
if (d.url) ui.info(` ${d.url}`);
|
|
89
|
+
if (d.error) ui.info(` ${ui.c.red(d.error)}`);
|
|
90
|
+
}
|
|
91
|
+
return 0;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function pad(s, n) {
|
|
95
|
+
return String(s).padEnd(n);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function parseLimit(raw) {
|
|
99
|
+
if (raw === undefined || raw === null || raw === '') return DEFAULT_LIMIT;
|
|
100
|
+
const n = Number(raw);
|
|
101
|
+
if (!Number.isInteger(n) || n < 1 || n > 20) return null;
|
|
102
|
+
return n;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
module.exports = deploys;
|
package/src/commands/diff.js
CHANGED
|
@@ -126,7 +126,7 @@ async function diffAll({ dir, projectId, baseURL, cfg, client, mgr }) {
|
|
|
126
126
|
config.persistCredsIfChanged(cfg, mgr.current);
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
-
const cmp = sync.compareTrees(local.files, tree.files);
|
|
129
|
+
const cmp = sync.refineByHash(sync.compareTrees(local.files, tree.files), { dir, projectId, baseURL });
|
|
130
130
|
|
|
131
131
|
// Un archivo ignorado localmente no es una diferencia que haya que resolver:
|
|
132
132
|
// el push no lo sube y el pull no lo baja. Se nombra aparte, no como "sólo
|
package/src/commands/init.js
CHANGED
|
@@ -106,6 +106,14 @@ async function init(args) {
|
|
|
106
106
|
const written = remote.write(dir, { projectId, baseURL });
|
|
107
107
|
ui.success(`Carpeta atada al proyecto ${ui.c.bold(projectId)}.`);
|
|
108
108
|
ui.info(ui.c.dim(`Remote guardado en ${written}`));
|
|
109
|
+
// Se lee siempre (ver walk.js), pero no decirlo hacía que la gente copiara
|
|
110
|
+
// a mano cada línea del .gitignore al .shiloignore.
|
|
111
|
+
if (fs.existsSync(path.join(dir, '.gitignore'))) {
|
|
112
|
+
ui.info(
|
|
113
|
+
ui.c.dim('Se respeta tu .gitignore: lo que git ignora tampoco se sube. ') +
|
|
114
|
+
ui.c.dim('.shiloignore es sólo para exclusiones extra.')
|
|
115
|
+
);
|
|
116
|
+
}
|
|
109
117
|
|
|
110
118
|
// --push/--pull no implican confirmación: el usuario pidió *encadenar* el
|
|
111
119
|
// comando, no saltearse su preflight. Sin --yes, push muestra el plan (y el
|