supcoflow 0.7.0 → 0.8.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 +209 -180
- package/dist/alias.js +60 -0
- package/dist/api.js +41 -19
- package/dist/argumentos.js +92 -85
- package/dist/cli.js +40 -35
- package/dist/comandos/clave-vigente.js +2 -2
- package/dist/comandos/claves.js +25 -25
- package/dist/comandos/configurar.js +40 -40
- package/dist/comandos/datos-y-politica.js +35 -23
- package/dist/comandos/describir.js +161 -151
- package/dist/comandos/guia.js +148 -61
- package/dist/comandos/instalacion.js +71 -40
- package/dist/comandos/login.js +25 -24
- package/dist/comandos/logout.js +17 -16
- package/dist/comandos/probar.js +141 -62
- package/dist/comandos/proyecto-del-repo.js +1 -1
- package/dist/comandos/proyecto.js +16 -16
- package/dist/comandos/quien-soy.js +8 -7
- package/dist/comandos/repo-conectar.js +9 -9
- package/dist/comandos/widget.js +9 -5
- package/dist/diagnostico.js +48 -0
- package/dist/entorno.js +19 -18
- package/dist/flags-por-comando.js +25 -21
- package/dist/guardado/archivo.js +2 -2
- package/dist/guardado/index.js +12 -8
- package/dist/guardado/llavero.js +5 -5
- package/dist/guia.js +3 -1
- package/dist/idempotencia.js +6 -6
- package/dist/instalacion.js +50 -11
- package/dist/lectura.js +10 -0
- package/dist/pruebas-guardadas.js +16 -10
- package/dist/repo-local.js +6 -6
- package/dist/secreto-a-archivo.js +6 -6
- package/package.json +1 -1
package/dist/comandos/login.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { FalloDeLaTerminal, resultadoDeFallo, SALIDA, taparSecretos } from "../contrato.js";
|
|
2
|
+
import { NOMBRE_DE_LA_FUENTE } from "../guardado/index.js";
|
|
2
3
|
import { PLAZO_MINIMO_MS, RESERVA_PARA_CERRAR_MS, sondearUnTramo, TRAMO_MAXIMO_MS } from "../sondeo.js";
|
|
3
4
|
const TECHO_DE_UN_PASO_MS = 5_000;
|
|
4
|
-
const VOLVER_A_EMPEZAR = "
|
|
5
|
+
const VOLVER_A_EMPEZAR = "Start over with `supcoflow login --start`.";
|
|
5
6
|
const fallo = (codigo, error, message, extra = {}) => ({
|
|
6
7
|
codigo, json: { ok: false, error, message, ...extra }, texto: message,
|
|
7
8
|
});
|
|
@@ -12,7 +13,7 @@ const motivo = (e) => e instanceof FalloDeLaTerminal ? e.message : taparSecretos
|
|
|
12
13
|
export async function reintentarPorRevocar(d, limite) {
|
|
13
14
|
const origen = d.api.origin;
|
|
14
15
|
const noSe = () => {
|
|
15
|
-
d.avisar("
|
|
16
|
+
d.avisar("I couldn't read the whole list of keys waiting to be revoked (the keychain didn't respond); I'll try again next time.");
|
|
16
17
|
};
|
|
17
18
|
let lectura;
|
|
18
19
|
try {
|
|
@@ -54,8 +55,8 @@ export async function reintentarPorRevocar(d, limite) {
|
|
|
54
55
|
export async function loginStart(d, flags) {
|
|
55
56
|
const origen = d.api.origin;
|
|
56
57
|
if (flags.dryRun) {
|
|
57
|
-
const haria = `
|
|
58
|
-
return { codigo: SALIDA.ok, json: { ok: true, dry_run: true, haria }, texto: `
|
|
58
|
+
const haria = `ask ${origen} for a device code`;
|
|
59
|
+
return { codigo: SALIDA.ok, json: { ok: true, dry_run: true, haria }, texto: `With --dry-run I do nothing; without it: ${haria}.` };
|
|
59
60
|
}
|
|
60
61
|
await reintentarPorRevocar(d, d.ahora() + 15_000);
|
|
61
62
|
let c;
|
|
@@ -75,7 +76,7 @@ export async function loginStart(d, flags) {
|
|
|
75
76
|
await d.guardado.guardarPendiente(origen, pendiente);
|
|
76
77
|
}
|
|
77
78
|
catch (e) {
|
|
78
|
-
return fallo(SALIDA.inesperado, e instanceof FalloDeLaTerminal ? e.error : "no_se_pudo_guardar", `
|
|
79
|
+
return fallo(SALIDA.inesperado, e instanceof FalloDeLaTerminal ? e.error : "no_se_pudo_guardar", `I couldn't save the login in progress: ${motivo(e)}`);
|
|
79
80
|
}
|
|
80
81
|
return {
|
|
81
82
|
codigo: SALIDA.esperandoAUnaPersona,
|
|
@@ -83,7 +84,7 @@ export async function loginStart(d, flags) {
|
|
|
83
84
|
ok: false, esperando: "persona", verification_uri: c.verificationUri,
|
|
84
85
|
verification_uri_complete: c.verificationUriComplete, user_code: c.userCode, expires_in: c.expiresIn,
|
|
85
86
|
},
|
|
86
|
-
texto: `
|
|
87
|
+
texto: `Open ${c.verificationUriComplete} and confirm the code ${c.userCode}. Then run \`supcoflow login --wait\`.`,
|
|
87
88
|
};
|
|
88
89
|
}
|
|
89
90
|
export async function loginWait(d) {
|
|
@@ -96,7 +97,7 @@ export async function loginWait(d) {
|
|
|
96
97
|
const techo = () => Math.max(0, Math.min(TECHO_DE_UN_PASO_MS, limite - d.ahora()));
|
|
97
98
|
const p = await d.guardado.leerPendiente(origen);
|
|
98
99
|
if (p === null) {
|
|
99
|
-
return fallo(SALIDA.entradaInvalida, "sin_login_en_curso", "
|
|
100
|
+
return fallo(SALIDA.entradaInvalida, "sin_login_en_curso", "There is no login in progress: start with `supcoflow login --start`.");
|
|
100
101
|
}
|
|
101
102
|
// La anterior se lee ANTES de sondear, así no consume la reserva del cierre. Se pide por
|
|
102
103
|
// `leerClaveCompleta` para saber de paso si SUPCOFLOW_TOKEN está definida (sin costo: la variable
|
|
@@ -110,9 +111,9 @@ export async function loginWait(d) {
|
|
|
110
111
|
// y sin borrar el pendiente, así el mismo `--wait` sirve cuando el llavero vuelva.
|
|
111
112
|
if (!lectura.completa) {
|
|
112
113
|
const salida = conVariable
|
|
113
|
-
? "
|
|
114
|
-
: "
|
|
115
|
-
return fallo(SALIDA.inesperado, "no_pude_leer_la_clave", `
|
|
114
|
+
? "unlock the keychain and run `supcoflow login --wait` again."
|
|
115
|
+
: "unlock the keychain and run `supcoflow login --wait` again (or, without logging in, export SUPCOFLOW_TOKEN with a valid key).";
|
|
116
|
+
return fallo(SALIDA.inesperado, "no_pude_leer_la_clave", `I couldn't read your saved key (the keychain didn't respond), and without knowing which one it is I can't replace it without leaving it active: ${salida}`);
|
|
116
117
|
}
|
|
117
118
|
const anterior = lectura.valor;
|
|
118
119
|
let fin;
|
|
@@ -132,26 +133,26 @@ export async function loginWait(d) {
|
|
|
132
133
|
return {
|
|
133
134
|
codigo: SALIDA.esperandoAUnaPersona,
|
|
134
135
|
json: { ok: false, esperando: "persona", user_code: fin.pendiente.userCode, verification_uri_complete: fin.pendiente.verificationUriComplete },
|
|
135
|
-
texto: `
|
|
136
|
+
texto: `Not confirmed yet. Open ${fin.pendiente.verificationUriComplete} and confirm the code ${fin.pendiente.userCode}; then run \`supcoflow login --wait\` again.`,
|
|
136
137
|
};
|
|
137
138
|
case "token": {
|
|
138
139
|
const r = await reemplazarClave(d, { clave: fin.clave, equipo: fin.equipo }, anterior?.clave ?? null, limite);
|
|
139
140
|
// Va acá y no adentro de `reemplazarClave` porque es acá donde se supo, antes de sondear, sin
|
|
140
141
|
// gastar reserva: preguntarlo al final costaría otra lectura lenta del guardado.
|
|
141
142
|
if (conVariable && r.json.guardada_en !== undefined) {
|
|
142
|
-
d.avisar("SUPCOFLOW_TOKEN
|
|
143
|
+
d.avisar("SUPCOFLOW_TOKEN is set and takes priority over the key I just saved.");
|
|
143
144
|
}
|
|
144
145
|
return r;
|
|
145
146
|
}
|
|
146
147
|
case "rechazado":
|
|
147
148
|
await d.guardado.borrarPendiente(origen, techo());
|
|
148
|
-
return fallo(SALIDA.haceFaltaUnaPersona, "acceso_rechazado", "
|
|
149
|
+
return fallo(SALIDA.haceFaltaUnaPersona, "acceso_rechazado", "Access was denied in the console; your previous session, if there was one, is unchanged.");
|
|
149
150
|
case "vencido":
|
|
150
151
|
await d.guardado.borrarPendiente(origen, techo());
|
|
151
|
-
return fallo(SALIDA.faltaLogin, "codigo_vencido", `
|
|
152
|
+
return fallo(SALIDA.faltaLogin, "codigo_vencido", `The code has expired. ${VOLVER_A_EMPEZAR}`);
|
|
152
153
|
case "invalido":
|
|
153
154
|
await d.guardado.borrarPendiente(origen, techo());
|
|
154
|
-
return fallo(SALIDA.faltaLogin, "codigo_invalido", `
|
|
155
|
+
return fallo(SALIDA.faltaLogin, "codigo_invalido", `The code is no longer valid. ${VOLVER_A_EMPEZAR}`);
|
|
155
156
|
}
|
|
156
157
|
}
|
|
157
158
|
// El login de una persona frente a la terminal: los mismos dos pasos, encadenados. Sin TTY (o con
|
|
@@ -160,14 +161,14 @@ export async function loginWait(d) {
|
|
|
160
161
|
const PAUSA_ENTRE_TRAMOS_MS = 1_000;
|
|
161
162
|
export async function loginInteractivo(d, flags) {
|
|
162
163
|
if (!d.esTty || flags.json) {
|
|
163
|
-
return fallo(SALIDA.entradaInvalida, "hace_falta_tty", "
|
|
164
|
+
return fallo(SALIDA.entradaInvalida, "hace_falta_tty", "Without an interactive terminal, use `supcoflow login --start --json` and then `supcoflow login --wait --json`.");
|
|
164
165
|
}
|
|
165
166
|
const inicio = await loginStart(d, flags);
|
|
166
167
|
if (inicio.codigo !== SALIDA.esperandoAUnaPersona)
|
|
167
168
|
return inicio;
|
|
168
169
|
// El texto de `--start` le dice a la persona que corra `--wait`; acá eso lo hace el comando solo.
|
|
169
170
|
const { verification_uri_complete: url, user_code: codigo } = inicio.json;
|
|
170
|
-
d.mostrar(`
|
|
171
|
+
d.mostrar(`Open ${String(url)} and confirm the code ${String(codigo)}. I'll wait here...`);
|
|
171
172
|
// Cada tramo termina por su cuenta (≤ 90 s) y el código vence a los pocos minutos, así que el
|
|
172
173
|
// bucle tiene fin aunque la persona nunca conteste: termina en `codigo_vencido`.
|
|
173
174
|
let r;
|
|
@@ -206,9 +207,9 @@ export async function reemplazarClave(d, nueva, anterior, limite) {
|
|
|
206
207
|
revocada = false;
|
|
207
208
|
}
|
|
208
209
|
const despues = revocada
|
|
209
|
-
? "
|
|
210
|
-
: "
|
|
211
|
-
return fallo(SALIDA.inesperado, "no_se_pudo_guardar", `
|
|
210
|
+
? "I revoked it so it doesn't stay active; your previous session, if there was one, is as it was."
|
|
211
|
+
: "I couldn't revoke it either: revoke it in the console, under \"Accesos desde la terminal\" (the console is in Spanish).";
|
|
212
|
+
return fallo(SALIDA.inesperado, "no_se_pudo_guardar", `I couldn't save the new key (${motivo(e)}). ${despues}`);
|
|
212
213
|
}
|
|
213
214
|
// 2. Revocar la anterior, o anotarla.
|
|
214
215
|
let anteriorRevocada = null;
|
|
@@ -228,10 +229,10 @@ export async function reemplazarClave(d, nueva, anterior, limite) {
|
|
|
228
229
|
try {
|
|
229
230
|
await d.guardado.anotarPorRevocar(origen, anterior, techo());
|
|
230
231
|
quedaPorRevocar = true;
|
|
231
|
-
d.avisar("
|
|
232
|
+
d.avisar("I couldn't revoke your previous key; it's noted down and I'll try again on the next login or logout.");
|
|
232
233
|
}
|
|
233
234
|
catch {
|
|
234
|
-
const message = "
|
|
235
|
+
const message = "I couldn't revoke or note down your previous key: revoke it in the console, under \"Accesos desde la terminal\" (the console is in Spanish).";
|
|
235
236
|
d.avisar(message);
|
|
236
237
|
if (queda() >= PLAZO_MINIMO_MS)
|
|
237
238
|
await d.guardado.borrarPendiente(origen, techo());
|
|
@@ -245,10 +246,10 @@ export async function reemplazarClave(d, nueva, anterior, limite) {
|
|
|
245
246
|
// 3. Borrar el pendiente; su `false` no cambia nada.
|
|
246
247
|
if (queda() >= PLAZO_MINIMO_MS)
|
|
247
248
|
await d.guardado.borrarPendiente(origen, techo());
|
|
248
|
-
const donde = guardadaEn === "llavero" ?
|
|
249
|
+
const donde = guardadaEn === "llavero" ? NOMBRE_DE_LA_FUENTE.llavero : NOMBRE_DE_LA_FUENTE.archivo;
|
|
249
250
|
return {
|
|
250
251
|
codigo: SALIDA.ok,
|
|
251
252
|
json: { ok: true, equipo: nueva.equipo, guardada_en: guardadaEn, anterior_revocada: anteriorRevocada, queda_por_revocar: quedaPorRevocar },
|
|
252
|
-
texto: `
|
|
253
|
+
texto: `Logged in to team ${nueva.equipo}; the key is saved in ${donde}.`,
|
|
253
254
|
};
|
|
254
255
|
}
|
package/dist/comandos/logout.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// (Review Focus 9 y 14). Borrar sin revocar dejaría la clave sirviendo en el motor y sin nadie que
|
|
3
3
|
// la recuerde: por eso, si el motor no confirma, no se borra nada y el comando sale 1.
|
|
4
4
|
import { FalloDeLaTerminal, SALIDA, taparSecretos } from "../contrato.js";
|
|
5
|
+
import { NOMBRE_DE_LA_FUENTE } from "../guardado/index.js";
|
|
5
6
|
import { reintentarPorRevocar } from "./login.js";
|
|
6
7
|
const motivo = (e) => e instanceof FalloDeLaTerminal ? e.message : taparSecretos(e instanceof Error ? e.message : String(e));
|
|
7
8
|
export async function comandoLogout(d, flags) {
|
|
@@ -9,7 +10,7 @@ export async function comandoLogout(d, flags) {
|
|
|
9
10
|
// Sin nada legible y un almacén que no contestó, no se sabe si hay sesión: decir «no había»
|
|
10
11
|
// dejaría creer que no queda ninguna clave viva (Review Focus 12).
|
|
11
12
|
const noPudeLeer = (extra = {}) => {
|
|
12
|
-
const message = "
|
|
13
|
+
const message = "I couldn't read the saved key (the keychain didn't respond); I didn't log out. Try again.";
|
|
13
14
|
return { codigo: SALIDA.inesperado, json: { ok: false, error: "no_pude_leer_la_clave", message, ...extra }, texto: message };
|
|
14
15
|
};
|
|
15
16
|
if (flags.dryRun) {
|
|
@@ -18,20 +19,20 @@ export async function comandoLogout(d, flags) {
|
|
|
18
19
|
if (vigente === null && !lectura.completa)
|
|
19
20
|
return noPudeLeer({ dry_run: true });
|
|
20
21
|
if (vigente === null) {
|
|
21
|
-
return { codigo: SALIDA.ok, json: { ok: true, dry_run: true, habia_sesion: false }, texto: "
|
|
22
|
+
return { codigo: SALIDA.ok, json: { ok: true, dry_run: true, habia_sesion: false }, texto: "You are not logged in: there is nothing to do." };
|
|
22
23
|
}
|
|
23
|
-
const haria = `
|
|
24
|
-
return { codigo: SALIDA.ok, json: { ok: true, dry_run: true, haria }, texto: `
|
|
24
|
+
const haria = `revoke the key in ${NOMBRE_DE_LA_FUENTE[vigente.fuente]} at ${origen}` + (vigente.fuente !== "env" ? " and delete it" : "");
|
|
25
|
+
return { codigo: SALIDA.ok, json: { ok: true, dry_run: true, haria }, texto: `With --dry-run I do nothing; without it: ${haria}.` };
|
|
25
26
|
}
|
|
26
27
|
// Lo pendiente de antes va primero: un logout es justo cuando la persona espera que no quede
|
|
27
28
|
// ninguna clave suya viva, y es la última oportunidad que tiene la terminal de revocarlas.
|
|
28
29
|
const { quedan } = await reintentarPorRevocar(d, d.ahora() + 15_000);
|
|
29
30
|
// `null` es «no lo pude comprobar» y se dice así: un 0 haría creer que no queda nada vivo.
|
|
30
31
|
if (quedan === null) {
|
|
31
|
-
d.avisar("
|
|
32
|
+
d.avisar("I couldn't check how many keys are left to revoke; any that exist are still noted down and I'll try again on the next login or logout.");
|
|
32
33
|
}
|
|
33
34
|
else if (quedan > 0) {
|
|
34
|
-
d.avisar(
|
|
35
|
+
d.avisar(`${quedan} previous key(s) are still not revoked; they are noted down and I'll try again on the next login or logout.`);
|
|
35
36
|
}
|
|
36
37
|
const lectura = await d.guardado.leerClaveCompleta(origen);
|
|
37
38
|
const vigente = lectura.valor;
|
|
@@ -40,22 +41,22 @@ export async function comandoLogout(d, flags) {
|
|
|
40
41
|
const conPendientes = (json) => ({ ...json, quedan_por_revocar: quedan, ...(lectura.completa ? {} : { lectura_completa: false }) });
|
|
41
42
|
if (vigente === null) {
|
|
42
43
|
await d.guardado.borrarPendiente(origen);
|
|
43
|
-
return { codigo: SALIDA.ok, json: conPendientes({ ok: true, habia_sesion: false }), texto: "
|
|
44
|
+
return { codigo: SALIDA.ok, json: conPendientes({ ok: true, habia_sesion: false }), texto: "You were not logged in." };
|
|
44
45
|
}
|
|
45
46
|
let revocada;
|
|
46
47
|
try {
|
|
47
48
|
revocada = (await d.revocarPropia(d.api, vigente.clave)) === "revocada";
|
|
48
49
|
}
|
|
49
50
|
catch (e) {
|
|
50
|
-
const cual = vigente.fuente === "env" ? "
|
|
51
|
-
const message = `
|
|
51
|
+
const cual = vigente.fuente === "env" ? "the key in SUPCOFLOW_TOKEN is still active" : "the key is still saved";
|
|
52
|
+
const message = `I couldn't revoke the key, so I didn't log out (${motivo(e)}); ${cual}. Try again.`;
|
|
52
53
|
const error = e instanceof FalloDeLaTerminal ? e.error : "inesperado";
|
|
53
54
|
return { codigo: SALIDA.inesperado, json: conPendientes({ ok: false, error, message }), texto: message };
|
|
54
55
|
}
|
|
55
56
|
// La variable no es nuestra: se revoca la clave que trae, pero lo guardado es OTRA credencial,
|
|
56
57
|
// que sólo se cierra revocándola a ella. Borrarla acá la dejaría viva y olvidada.
|
|
57
58
|
if (vigente.fuente === "env") {
|
|
58
|
-
d.avisar("
|
|
59
|
+
d.avisar("I revoked the key in SUPCOFLOW_TOKEN; remove it from your environment.");
|
|
59
60
|
// `undefined` = no se pudo leer: no se afirma que no haya, el json lleva `null` en vez de mentir.
|
|
60
61
|
let guardada;
|
|
61
62
|
try {
|
|
@@ -66,19 +67,19 @@ export async function comandoLogout(d, flags) {
|
|
|
66
67
|
guardada = undefined;
|
|
67
68
|
}
|
|
68
69
|
if (guardada) {
|
|
69
|
-
d.avisar(`
|
|
70
|
+
d.avisar(`Another key is still saved in ${NOMBRE_DE_LA_FUENTE[guardada.fuente]}; to log it out, run \`supcoflow logout\` without SUPCOFLOW_TOKEN.`);
|
|
70
71
|
}
|
|
71
72
|
return {
|
|
72
73
|
codigo: SALIDA.ok,
|
|
73
74
|
json: conPendientes({ ok: true, revocada, fuente: "env", queda_clave_guardada: guardada === undefined ? null : guardada !== null }),
|
|
74
|
-
texto: "
|
|
75
|
+
texto: "I revoked the key in SUPCOFLOW_TOKEN.",
|
|
75
76
|
};
|
|
76
77
|
}
|
|
77
78
|
// Con la lectura incompleta se borra sólo la copia que se leyó y se acaba de revocar: la del
|
|
78
79
|
// almacén que no contestó puede ser otra clave, viva, y borrarla sin revocarla la olvidaría.
|
|
79
80
|
if (!lectura.completa) {
|
|
80
|
-
const otro = vigente.fuente === "archivo" ?
|
|
81
|
-
d.avisar(`
|
|
81
|
+
const otro = vigente.fuente === "archivo" ? NOMBRE_DE_LA_FUENTE.llavero : NOMBRE_DE_LA_FUENTE.archivo;
|
|
82
|
+
d.avisar(`I'm logging out the key in ${NOMBRE_DE_LA_FUENTE[vigente.fuente]}, but I couldn't read ${otro}: another copy may still be saved there; when it responds, run \`supcoflow logout\` again.`);
|
|
82
83
|
}
|
|
83
84
|
try {
|
|
84
85
|
if (lectura.completa)
|
|
@@ -88,9 +89,9 @@ export async function comandoLogout(d, flags) {
|
|
|
88
89
|
}
|
|
89
90
|
catch (e) {
|
|
90
91
|
// La clave ya no sirve en el motor, pero quedó escrita: el comando no puede decir que terminó bien.
|
|
91
|
-
const message = `
|
|
92
|
+
const message = `The key was revoked but I couldn't delete it: ${motivo(e)}`;
|
|
92
93
|
return { codigo: SALIDA.inesperado, json: conPendientes({ ok: false, error: "no_se_pudo_borrar", message }), texto: message };
|
|
93
94
|
}
|
|
94
95
|
await d.guardado.borrarPendiente(origen);
|
|
95
|
-
return { codigo: SALIDA.ok, json: conPendientes({ ok: true, revocada }), texto: "
|
|
96
|
+
return { codigo: SALIDA.ok, json: conPendientes({ ok: true, revocada }), texto: "Logged out." };
|
|
96
97
|
}
|