supcoflow 0.8.0 → 0.9.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 +112 -16
- package/dist/api.js +53 -4
- package/dist/argumentos.js +7 -2
- package/dist/cli.js +5 -2
- package/dist/comandos/contexto.js +102 -0
- package/dist/comandos/describir.js +18 -17
- package/dist/comandos/guia.js +3 -2
- package/dist/comandos/instalacion.js +70 -28
- package/dist/comandos/probar.js +101 -7
- package/dist/comandos/proyecto-del-repo.js +11 -1
- package/dist/comandos/proyecto.js +34 -20
- package/dist/comandos/widget.js +26 -0
- package/dist/diagnostico.js +46 -1
- package/dist/entorno.js +47 -9
- package/dist/flags-por-comando.js +5 -1
- package/dist/guardado/lugar.js +63 -0
- package/dist/guardado/protegido.js +38 -0
- package/dist/guardado/repo.js +98 -0
- package/dist/idempotencia.js +15 -27
- package/dist/instalacion.js +88 -15
- package/dist/pruebas-guardadas.js +28 -16
- package/dist/repo-local.js +90 -8
- package/package.json +1 -1
package/dist/repo-local.js
CHANGED
|
@@ -30,6 +30,52 @@ export function repoDelOrigin(url) {
|
|
|
30
30
|
}
|
|
31
31
|
return null;
|
|
32
32
|
}
|
|
33
|
+
// El `origin` con forma de alias de SSH (`git@github-gf0:dueño/repo`, `ssh://git@github-gf0/dueño/repo`): el host no
|
|
34
|
+
// es github.com, pero el dueño/repo sí tiene la forma de uno de GitHub. NO se resuelve (no se corre `ssh` ni se lee su
|
|
35
|
+
// configuración: `Match exec` ejecutaría programas de la persona): sólo se le dice qué asociar a mano, una vez.
|
|
36
|
+
const FORMAS_DE_ALIAS = [
|
|
37
|
+
/^[A-Za-z0-9._-]+@([A-Za-z0-9._-]+):([^/\s]+)\/([^/\s]+?)(?:\.git)?\/?$/,
|
|
38
|
+
/^ssh:\/\/[A-Za-z0-9._-]+@([A-Za-z0-9._-]+)(?::\d+)?\/([^/\s]+)\/([^/\s]+?)(?:\.git)?\/?$/,
|
|
39
|
+
];
|
|
40
|
+
export function aliasDelOrigin(url) {
|
|
41
|
+
const limpia = url.trim();
|
|
42
|
+
for (const forma of FORMAS_DE_ALIAS) {
|
|
43
|
+
const m = forma.exec(limpia);
|
|
44
|
+
if (!m)
|
|
45
|
+
continue;
|
|
46
|
+
const repo = `${m[2]}/${m[3]}`;
|
|
47
|
+
if (m[1].toLowerCase() === "github.com" || !esRepoValido(repo))
|
|
48
|
+
return null;
|
|
49
|
+
return { host: m[1], repo };
|
|
50
|
+
}
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
// Lo que se muestra de un `origin`: nunca el usuario:clave@ de una URL (podría ser un token). Las credenciales se quitan de la
|
|
54
|
+
// URL ENTERA y recién después se limita el tamaño (recortar antes dejaría pasar un `@` lejano con su clave a medias). Lo que no se
|
|
55
|
+
// puede interpretar con seguridad no se muestra crudo: sale un marcador neutro.
|
|
56
|
+
const TOPE_DEL_ORIGEN_MOSTRADO = 200;
|
|
57
|
+
export function origenParaMostrar(url) {
|
|
58
|
+
const limpia = url.trim();
|
|
59
|
+
const url_ = /^([A-Za-z][A-Za-z0-9+.-]*:\/\/)([^/\s]*)(.*)$/s.exec(limpia);
|
|
60
|
+
let mostrable = null;
|
|
61
|
+
if (url_) {
|
|
62
|
+
// La autoridad llega hasta la primera `/`; el usuario:clave es todo lo anterior al ÚLTIMO `@` de la autoridad.
|
|
63
|
+
const arroba = url_[2].lastIndexOf("@");
|
|
64
|
+
const usuario = arroba === -1 ? "" : url_[2].slice(0, arroba).split(":")[0];
|
|
65
|
+
const host = arroba === -1 ? url_[2] : url_[2].slice(arroba + 1);
|
|
66
|
+
mostrable = `${url_[1]}${usuario === "git" ? "git@" : ""}${host}${url_[3]}`;
|
|
67
|
+
}
|
|
68
|
+
else if (/^[A-Za-z0-9._-]+@[A-Za-z0-9._-]+:/.test(limpia)) {
|
|
69
|
+
mostrable = limpia; // `git@host:o/r`: el usuario de un scp no lleva clave
|
|
70
|
+
}
|
|
71
|
+
if (mostrable === null || /\s/.test(mostrable))
|
|
72
|
+
return "(an address I don't recognize)";
|
|
73
|
+
return mostrable.length > TOPE_DEL_ORIGEN_MOSTRADO ? mostrable.slice(0, TOPE_DEL_ORIGEN_MOSTRADO) + "…" : mostrable;
|
|
74
|
+
}
|
|
75
|
+
// El remoto de asociación explícita: la persona le dice una sola vez a qué repo de GitHub corresponde este clon cuando su
|
|
76
|
+
// `origin` no es una dirección de GitHub (alias de SSH, otro servidor). Con las mismas formas estrictas de `origin`;
|
|
77
|
+
// nunca se usa para traer nada.
|
|
78
|
+
export const REMOTO_DE_ASOCIACION = "supcoflow";
|
|
33
79
|
export async function leerRepoLocal(git, cwd) {
|
|
34
80
|
const top = await git(["rev-parse", "--show-toplevel"], cwd);
|
|
35
81
|
const raiz = top.codigo === 0 ? top.stdout.trim() : "";
|
|
@@ -37,32 +83,68 @@ export async function leerRepoLocal(git, cwd) {
|
|
|
37
83
|
return { repo: null, raiz: null, motivo: "sin_git" };
|
|
38
84
|
const origin = await git(["remote", "get-url", "origin"], cwd);
|
|
39
85
|
const url = origin.codigo === 0 ? origin.stdout.trim() : "";
|
|
86
|
+
const repoDelOrigen = url ? repoDelOrigin(url) : null;
|
|
87
|
+
// Si `origin` es de GitHub, gana `origin`, como siempre.
|
|
88
|
+
if (repoDelOrigen)
|
|
89
|
+
return { repo: repoDelOrigen, raiz };
|
|
90
|
+
const asociado = await git(["remote", "get-url", REMOTO_DE_ASOCIACION], cwd);
|
|
91
|
+
const urlAsociada = asociado.codigo === 0 ? asociado.stdout.trim() : "";
|
|
92
|
+
const repoAsociado = urlAsociada ? repoDelOrigin(urlAsociada) : null;
|
|
93
|
+
if (repoAsociado)
|
|
94
|
+
return { repo: repoAsociado, raiz };
|
|
95
|
+
const alias = url ? aliasDelOrigin(url) : null;
|
|
96
|
+
const comun = { repo: null, raiz, ...(url ? { origen: origenParaMostrar(url) } : {}), ...(urlAsociada ? { hayRemotoSupcoflow: true } : {}) };
|
|
40
97
|
if (!url)
|
|
41
|
-
return {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
98
|
+
return { ...comun, motivo: "sin_origin" };
|
|
99
|
+
if (alias)
|
|
100
|
+
return { ...comun, motivo: "origin_alias_ssh", alias };
|
|
101
|
+
return { ...comun, motivo: "origin_no_es_github" };
|
|
102
|
+
}
|
|
103
|
+
// El comando exacto que asocia este clon con un repo de GitHub. `add` si todavía no hay remoto `supcoflow`; `set-url` si
|
|
104
|
+
// hay uno que no es una dirección de GitHub.
|
|
105
|
+
export function comandoDeAsociacion(repo, hayRemoto) {
|
|
106
|
+
return `git remote ${hayRemoto ? "set-url" : "add"} ${REMOTO_DE_ASOCIACION} https://github.com/${repo}.git`;
|
|
46
107
|
}
|
|
47
108
|
const MOTIVOS = {
|
|
48
109
|
sin_git: "this folder isn't a git repo",
|
|
49
110
|
sin_origin: "the repo has no remote called \"origin\"",
|
|
50
111
|
origin_no_es_github: "the \"origin\" remote isn't a GitHub repo",
|
|
112
|
+
origin_alias_ssh: "the \"origin\" remote isn't a GitHub repo",
|
|
51
113
|
};
|
|
114
|
+
// La frase que dice la verdad sobre un clon que no se puede atar a `repo` (owner/repo de GitHub) y cómo atarlo.
|
|
115
|
+
export function frasePorQueNoAtaClon(local, repo) {
|
|
116
|
+
if (local.motivo === "sin_git")
|
|
117
|
+
return "This folder isn't a git clone.";
|
|
118
|
+
const sugerido = repo ?? local.alias?.repo ?? null;
|
|
119
|
+
const como = sugerido === null ? ""
|
|
120
|
+
: ` If this is ${sugerido} on GitHub, tell me once: \`${comandoDeAsociacion(sugerido, local.hayRemotoSupcoflow === true)}\` (it's never fetched).`;
|
|
121
|
+
if (local.motivo === "origin_alias_ssh" && repo === null && local.alias) {
|
|
122
|
+
return `Your origin uses the SSH alias \`${local.alias.host}\`, which I don't resolve.${como}`;
|
|
123
|
+
}
|
|
124
|
+
const clon = local.origen ? `This clone is \`${local.origen}\`, which I can't tie to github.com${repo ? `/${repo}` : ""}.` : "This clone has no \"origin\" remote I can tie to GitHub.";
|
|
125
|
+
return `${clon}${como}`;
|
|
126
|
+
}
|
|
52
127
|
export async function repoPedido(d) {
|
|
53
128
|
const local = await leerRepoLocal(d.git, d.cwd);
|
|
54
129
|
if (d.flagRepo !== null) {
|
|
55
130
|
if (!esRepoValido(d.flagRepo)) {
|
|
56
131
|
throw new FalloDeLaTerminal(SALIDA.entradaInvalida, "repo_invalido", "The value of --repo must be owner/repo (for example owner/my-app), without the URL.");
|
|
57
132
|
}
|
|
58
|
-
// La raíz sólo se da si el
|
|
133
|
+
// La raíz sólo se da si el repo local reconocido es ESE repo: con otro, lo que se escriba en esta
|
|
59
134
|
// carpeta (el archivo del proyecto) no le pertenece.
|
|
60
135
|
const mismo = local.repo !== null && local.repo.toLowerCase() === d.flagRepo.toLowerCase();
|
|
61
|
-
|
|
136
|
+
if (mismo)
|
|
137
|
+
return { repo: d.flagRepo, raiz: local.raiz };
|
|
138
|
+
// El mensaje de «no es tu clon» dice por qué, y deja el comando exacto para atarlo si la persona SABE que lo es.
|
|
139
|
+
const sinClon = local.repo !== null
|
|
140
|
+
? `This clone is github.com/${local.repo}, not ${d.flagRepo}.`
|
|
141
|
+
: frasePorQueNoAtaClon(local, d.flagRepo);
|
|
142
|
+
return { repo: d.flagRepo, raiz: null, sinClon };
|
|
62
143
|
}
|
|
63
144
|
if (local.repo === null) {
|
|
64
145
|
const porque = local.motivo ? MOTIVOS[local.motivo] : "the folder couldn't be read";
|
|
65
|
-
|
|
146
|
+
const detalle = local.motivo === "origin_alias_ssh" ? ` ${frasePorQueNoAtaClon(local, null)}` : "";
|
|
147
|
+
throw new FalloDeLaTerminal(SALIDA.entradaInvalida, "sin_repo", `I couldn't find a GitHub repo here (${porque}).${detalle} Pass --repo owner/repo.`);
|
|
66
148
|
}
|
|
67
149
|
return { repo: local.repo, raiz: local.raiz };
|
|
68
150
|
}
|
package/package.json
CHANGED