xonecode 0.23.0 → 0.26.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/THIRD_PARTY_NOTICES.md +32 -0
- package/apps/web/dist/assets/EditorDeFichero-BOMIz5Wx.js +38 -0
- package/apps/web/dist/assets/EditorDeFichero-BX_LfESa.css +1 -0
- package/apps/web/dist/assets/index-DICdq4UC.css +1 -0
- package/apps/web/dist/assets/index-DTsPoUh_.js +499 -0
- package/apps/web/dist/assets/{php-CQxmz2B6.js → php-BHWCwDKR.js} +1 -1
- package/apps/web/dist/assets/{ruby-CipCmGZU.js → ruby-D3HLU4IP.js} +1 -1
- package/apps/web/dist/index.html +2 -2
- package/dist/agent/cloudstudio/cloudstudioMcp.js +3 -2
- package/dist/agent/cloudstudio/cloudstudioMcp.js.map +1 -1
- package/dist/agent/cloudstudio/subida.js +22 -7
- package/dist/agent/cloudstudio/subida.js.map +1 -1
- package/dist/agent/conectores/conectoresEnDisco.js +3 -2
- package/dist/agent/conectores/conectoresEnDisco.js.map +1 -1
- package/dist/agent/config/configEnDisco.js +5 -2
- package/dist/agent/config/configEnDisco.js.map +1 -1
- package/dist/agent/config/settingsEnDisco.js +2 -1
- package/dist/agent/config/settingsEnDisco.js.map +1 -1
- package/dist/agent/grafo/arbolDeProyecto.js +189 -20
- package/dist/agent/grafo/arbolDeProyecto.js.map +1 -1
- package/dist/agent/motores/trueforge/memoriaTrueforge.js +2 -1
- package/dist/agent/motores/trueforge/memoriaTrueforge.js.map +1 -1
- package/dist/agent/renombrarSobre.js +72 -0
- package/dist/agent/renombrarSobre.js.map +1 -0
- package/dist/agent/sesiones/baseDeFichero.js +90 -0
- package/dist/agent/sesiones/baseDeFichero.js.map +1 -0
- package/dist/agent/sesiones/gitSync.js +166 -1
- package/dist/agent/sesiones/gitSync.js.map +1 -1
- package/dist/cli/aprobar.js.map +1 -1
- package/dist/cli/consola.js +58 -9
- package/dist/cli/consola.js.map +1 -1
- package/dist/cli/main.js +13 -2
- package/dist/cli/main.js.map +1 -1
- package/dist/core/actos.js.map +1 -1
- package/dist/core/cloudstudio.js +15 -1
- package/dist/core/cloudstudio.js.map +1 -1
- package/dist/core/descriptoresDeApp.js +27 -1
- package/dist/core/descriptoresDeApp.js.map +1 -1
- package/dist/core/imagenesDeDocumento.js +7 -0
- package/dist/core/imagenesDeDocumento.js.map +1 -1
- package/dist/core/settings.js +25 -0
- package/dist/core/settings.js.map +1 -1
- package/dist/web/servidor/arranque.js +316 -22
- package/dist/web/servidor/arranque.js.map +1 -1
- package/dist/web/servidor/consolaWeb.js +32 -0
- package/dist/web/servidor/consolaWeb.js.map +1 -1
- package/dist/web/servidor/sesiones.js +5 -4
- package/dist/web/servidor/sesiones.js.map +1 -1
- package/dist/web/servidor/transporte.js.map +1 -1
- package/dist/web/servidor/vestibulo.js +5 -1
- package/dist/web/servidor/vestibulo.js.map +1 -1
- package/package.json +1 -1
- package/apps/web/dist/assets/index-CLQDE24i.js +0 -490
- package/apps/web/dist/assets/index-gpgG7Ir4.css +0 -1
|
@@ -75,17 +75,18 @@ import { cloudstudioDelProyecto, guardarModoDeEscrituraDeProyecto, modoDeEscritu
|
|
|
75
75
|
import { abrirEnSistema, olvidarEntorno as olvidarCredencialesDeEntorno, rutaAuthPorDefecto } from "../../agent/cloudstudio/cloudstudioMcp.js";
|
|
76
76
|
import { nombreDePersona } from "../../agent/config/persona.js";
|
|
77
77
|
import { cambiosDeSesion, fotoDeApertura, olvidarSesion, parcheDeSesion } from "../../agent/sesiones/sesionGit.js";
|
|
78
|
-
import { commitDeTurno, cambiosPendientes, trabajoSinCommitear } from "../../agent/sesiones/gitSync.js";
|
|
78
|
+
import { commitDeTurno, cambiosPendientes, parcheDeSubida, pendientesDeSubida, sinSeguir, trabajoSinCommitear, } from "../../agent/sesiones/gitSync.js";
|
|
79
79
|
import { marcarTareaDeSesion, sembrarConsumosPendientes } from "./sesiones.js";
|
|
80
80
|
import { RUTA_ARTEFACTOS, esRutaDeArtefacto } from "../../core/artefactos.js";
|
|
81
|
-
import { arbolDeProyecto, leerFicheroDeProyecto, mimeDeImagen, motivoDeRutaInaceptable, } from "../../agent/grafo/arbolDeProyecto.js";
|
|
81
|
+
import { arbolDeProyecto, escribirFicheroDeProyecto, iconoDelProyecto, leerFicheroDeProyecto, mimeDeImagen, motivoDeRutaInaceptable, motivoParaNoEnsenar, } from "../../agent/grafo/arbolDeProyecto.js";
|
|
82
|
+
import { baseDeFichero } from "../../agent/sesiones/baseDeFichero.js";
|
|
82
83
|
import { modeloEnDisco } from "../../agent/navegacion/indiceEnDisco.js";
|
|
83
84
|
import { ficherosDelProyecto } from "../../agent/turno/ficherosDelProyecto.js";
|
|
84
85
|
import { fotoDeColecciones } from "../../core/fotoDeColecciones.js";
|
|
85
86
|
import { modeloDelCambio } from "../../agent/sesiones/modeloDelCambio.js";
|
|
86
87
|
import { planesDelProyecto } from "../../agent/planesEnDisco.js";
|
|
87
88
|
import { CSP_DE_OPENUI, documentoDeOpenui, esArtefactoOpenui, temaDeVisor } from "./visorOpenui.js";
|
|
88
|
-
import { RUTA_IMAGEN_DEL_PROYECTO } from "../../core/imagenesDeDocumento.js";
|
|
89
|
+
import { RUTA_ICONO_DEL_PROYECTO, RUTA_IMAGEN_DEL_PROYECTO } from "../../core/imagenesDeDocumento.js";
|
|
89
90
|
import { leerArtefactoCrudo, leerArtefactoDeSesion, } from "../../agent/grafo/artefactosEnDisco.js";
|
|
90
91
|
import { CatalogoModelos, baseUrlDeOllama, baseUrlDeOllamaCloud, capacidadesDeOllama, } from "../../agent/config/catalogoModelos.js";
|
|
91
92
|
import { Modelos } from "../../agent/config/modelos.js";
|
|
@@ -452,6 +453,31 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
452
453
|
clearTimeout(reloj);
|
|
453
454
|
}
|
|
454
455
|
};
|
|
456
|
+
/**
|
|
457
|
+
* CUÁL es el proyecto abierto, deducido de su raíz: la que le tocaría a cada proyecto de la
|
|
458
|
+
* lista se calcula con la misma función que la creó. Guardar el id aparte al abrirlo sería una
|
|
459
|
+
* segunda fuente de verdad que se queda vieja el día que alguien abra por otro camino.
|
|
460
|
+
*
|
|
461
|
+
* Lo usan el alta (`proyectoActivo`) y el guardado del editor, que compara con él el proyecto
|
|
462
|
+
* con que se ABRIÓ la edición: tiene que ser la MISMA cuenta, o el editor y el servidor
|
|
463
|
+
* podrían no estar de acuerdo sobre cuál es «el abierto».
|
|
464
|
+
*
|
|
465
|
+
* El `entorno` se copia a una constante porque TypeScript no puede saber que `entornoElegido`
|
|
466
|
+
* —una variable del cierre, que otro mensaje puede cambiar— sigue definida dentro del callback.
|
|
467
|
+
*/
|
|
468
|
+
const proyectoActivoDe = (abierto) => {
|
|
469
|
+
const entorno = entornoElegido;
|
|
470
|
+
if (abierto === undefined || entorno === undefined)
|
|
471
|
+
return undefined;
|
|
472
|
+
return proyectos.find((p) => {
|
|
473
|
+
try {
|
|
474
|
+
return vestibulo.raizDeProyecto(entorno, p.nombre) === abierto.raiz;
|
|
475
|
+
}
|
|
476
|
+
catch {
|
|
477
|
+
return false;
|
|
478
|
+
}
|
|
479
|
+
})?.id;
|
|
480
|
+
};
|
|
455
481
|
/**
|
|
456
482
|
* El mensaje de alta: qué falta, y con qué elegirlo.
|
|
457
483
|
*
|
|
@@ -481,24 +507,11 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
481
507
|
// puede escribirlo después de abrir (el alta de un proyecto cloud), y una copia
|
|
482
508
|
// tomada antes se quedaría diciendo lo de antes.
|
|
483
509
|
const modo = abierto === undefined ? undefined : modoDeProyecto(abierto.raiz);
|
|
484
|
-
// CUÁL es el abierto, deducido de su raíz
|
|
485
|
-
//
|
|
486
|
-
//
|
|
487
|
-
// otro camino.
|
|
488
|
-
// El `entorno` se copia a una constante porque TypeScript no puede saber que
|
|
489
|
-
// `entornoElegido` —una variable del cierre, que otro mensaje puede cambiar— sigue
|
|
490
|
-
// definida dentro del callback.
|
|
510
|
+
// CUÁL es el abierto, deducido de su raíz (`proyectoActivoDe`). El `entorno` se copia a una
|
|
511
|
+
// constante porque `entornoElegido` es una variable del cierre que otro mensaje puede cambiar,
|
|
512
|
+
// y TypeScript no sabría que sigue definida dentro de los callbacks de abajo.
|
|
491
513
|
const entorno = entornoElegido;
|
|
492
|
-
const activo = abierto
|
|
493
|
-
? undefined
|
|
494
|
-
: proyectos.find((p) => {
|
|
495
|
-
try {
|
|
496
|
-
return vestibulo.raizDeProyecto(entorno, p.nombre) === abierto.raiz;
|
|
497
|
-
}
|
|
498
|
-
catch {
|
|
499
|
-
return false;
|
|
500
|
-
}
|
|
501
|
-
})?.id;
|
|
514
|
+
const activo = proyectoActivoDe(abierto);
|
|
502
515
|
const pendientes = proyectoAbierto ? [] : await vestibulo.pasosPendientes();
|
|
503
516
|
// Lo que ya había sin commitear al ABRIR. `abierto` se capturó arriba, antes de este
|
|
504
517
|
// `await`: `anunciarAlta` no va en la cola del vestíbulo, así que entre medias puede
|
|
@@ -534,6 +547,22 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
534
547
|
return undefined;
|
|
535
548
|
}
|
|
536
549
|
};
|
|
550
|
+
/**
|
|
551
|
+
* Qué proyectos BAJADOS tienen icono de app, una vez por anuncio y antes de componerlo (la
|
|
552
|
+
* lista se compone síncrona). Por el cable solo viaja el booleano: qué fichero es lo vuelve a
|
|
553
|
+
* decidir `RUTA_ICONO_DEL_PROYECTO` al servirlo, y ninguna ruta sale del host.
|
|
554
|
+
*/
|
|
555
|
+
const conIcono = new Set();
|
|
556
|
+
const buscarIcono = opciones.iconoDelProyecto;
|
|
557
|
+
if (buscarIcono !== undefined) {
|
|
558
|
+
await Promise.all(proyectos.map(async (p) => {
|
|
559
|
+
if (!hayCopiaLocal(p.nombre))
|
|
560
|
+
return;
|
|
561
|
+
const raiz = raizDeProyectoOSilencio(p.nombre);
|
|
562
|
+
if (raiz !== undefined && (await buscarIcono(raiz).catch(() => undefined)) !== undefined)
|
|
563
|
+
conIcono.add(p.id);
|
|
564
|
+
}));
|
|
565
|
+
}
|
|
537
566
|
emitir({
|
|
538
567
|
clase: "alta",
|
|
539
568
|
pasos,
|
|
@@ -593,6 +622,8 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
593
622
|
const rama = raiz === undefined ? undefined : cloudstudioDelProyecto(raiz)?.rama;
|
|
594
623
|
return typeof rama === "string" && rama !== "" ? { rama } : {};
|
|
595
624
|
})(),
|
|
625
|
+
// El icono de la app, solo si la copia tiene uno que se pueda enseñar (ver `conIcono`).
|
|
626
|
+
...(conIcono.has(p.id) ? { icono: true } : {}),
|
|
596
627
|
};
|
|
597
628
|
}),
|
|
598
629
|
ramas,
|
|
@@ -2775,7 +2806,23 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
2775
2806
|
return;
|
|
2776
2807
|
}
|
|
2777
2808
|
const { via, ficheros, mezclados } = await opciones.cambiosDeSesion(abierto.raiz, sesion);
|
|
2778
|
-
|
|
2809
|
+
// Lo pendiente de subir se marca AQUÍ, en la misma foto que la lista: cruzarlo en el
|
|
2810
|
+
// cliente con el `sync` —que llega en otro momento— escondería lo recién escrito.
|
|
2811
|
+
const marcados = await marcarPendientes(abierto.raiz, ficheros);
|
|
2812
|
+
emitir({ clase: "revision", via, ficheros: marcados, ...(mezclados === undefined ? {} : { mezclados }) });
|
|
2813
|
+
};
|
|
2814
|
+
/**
|
|
2815
|
+
* Lo pendiente de subir del proyecto ABIERTO —el mismo que sincroniza `atenderSync`—: la
|
|
2816
|
+
* lista sin `ruta`, el diff de uno con ella. Las reglas viven en `lecturaDePendientesDeSubida`
|
|
2817
|
+
* y `lecturaDeParcheDeSubida`, exportadas y probadas contra git de verdad.
|
|
2818
|
+
*/
|
|
2819
|
+
const atenderPendientesDeSubida = async (ruta) => {
|
|
2820
|
+
const abierto = vestibulo.proyectoAbierto();
|
|
2821
|
+
if (abierto === undefined) {
|
|
2822
|
+
emitir({ clase: "pendientesDeSubida", error: "no hay ningún proyecto abierto" });
|
|
2823
|
+
return;
|
|
2824
|
+
}
|
|
2825
|
+
emitir(ruta === undefined ? await lecturaDePendientesDeSubida(abierto.raiz) : await lecturaDeParcheDeSubida(abierto.raiz, ruta));
|
|
2779
2826
|
};
|
|
2780
2827
|
/**
|
|
2781
2828
|
* La sincronización con CloudStudio del proyecto abierto, pedida desde un control
|
|
@@ -3758,6 +3805,74 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
3758
3805
|
});
|
|
3759
3806
|
}
|
|
3760
3807
|
};
|
|
3808
|
+
/**
|
|
3809
|
+
* Guardar lo que la persona editó. Tres cosas antes de tocar el disco, y cada una contesta en
|
|
3810
|
+
* vez de callar —el editor tiene un «Guardando…» encendido esperando—:
|
|
3811
|
+
* - que haya proyecto abierto y puerto que escriba;
|
|
3812
|
+
* - que nadie trabaje en esa RAÍZ (`vestibulo.motivoParaNoEditar`): un turno o una tarea que
|
|
3813
|
+
* escribe en la misma copia pisaría, o sería pisado, sin avisar;
|
|
3814
|
+
* - lo demás —guardas de ruta, huella, tope— lo decide el puerto y lo DEVUELVE.
|
|
3815
|
+
* El `try` es el de `atenderFichero`: un fallo inesperado contesta sin la ruta de la máquina.
|
|
3816
|
+
*/
|
|
3817
|
+
const atenderGuardarFichero = async (ruta, texto, huella, id, proyecto) => {
|
|
3818
|
+
const abierto = vestibulo.proyectoAbierto();
|
|
3819
|
+
if (abierto === undefined) {
|
|
3820
|
+
emitir({ clase: "ficheroGuardado", ruta, id, error: "no hay ningún proyecto abierto" });
|
|
3821
|
+
return;
|
|
3822
|
+
}
|
|
3823
|
+
// El guardado va al proyecto del FOCO, que puede haber cambiado desde que se abrió la edición
|
|
3824
|
+
// (otra pestaña abre otro, se cambia de entorno). La huella no lo frena si el contenido es
|
|
3825
|
+
// idéntico —el mismo proyecto en dos entornos, dos proyectos del mismo esqueleto—, así que se
|
|
3826
|
+
// compara el proyecto con que se abrió con el de ahora, con la MISMA cuenta que el alta. Si no
|
|
3827
|
+
// se puede deducir, tampoco se escribe: no se sabe dónde caería.
|
|
3828
|
+
if (proyectoActivoDe(abierto) !== proyecto) {
|
|
3829
|
+
emitir({ clase: "ficheroGuardado", ruta, id, error: "el proyecto abierto ya no es el de este fichero: vuelve a abrirlo" });
|
|
3830
|
+
return;
|
|
3831
|
+
}
|
|
3832
|
+
if (opciones.escribirFichero === undefined) {
|
|
3833
|
+
emitir({ clase: "ficheroGuardado", ruta, id, error: "esta ejecución no puede escribir en el proyecto" });
|
|
3834
|
+
return;
|
|
3835
|
+
}
|
|
3836
|
+
const ocupado = vestibulo.motivoParaNoEditar(abierto.raiz);
|
|
3837
|
+
if (ocupado !== undefined) {
|
|
3838
|
+
emitir({ clase: "ficheroGuardado", ruta, id, error: ocupado });
|
|
3839
|
+
return;
|
|
3840
|
+
}
|
|
3841
|
+
try {
|
|
3842
|
+
// `id` lo pone la pestaña en CADA guardado y vuelve en la respuesta: esta va a todas las
|
|
3843
|
+
// pestañas, y cada una reconoce la suya por él. La huella no vale —dos pestañas que parten de
|
|
3844
|
+
// la misma versión mandan la misma—.
|
|
3845
|
+
emitir({ clase: "ficheroGuardado", ...(await opciones.escribirFichero(abierto.raiz, ruta, texto, huella)), id });
|
|
3846
|
+
}
|
|
3847
|
+
catch (error) {
|
|
3848
|
+
informar(`no se pudo guardar «${ruta}» (${codigoDe(error)})`);
|
|
3849
|
+
emitir({ clase: "ficheroGuardado", ruta, id, error: "no se pudo guardar el fichero" });
|
|
3850
|
+
}
|
|
3851
|
+
};
|
|
3852
|
+
/**
|
|
3853
|
+
* La base de las marcas del editor. Con la sesión ABIERTA (`abierto.sesion`, la misma que mira
|
|
3854
|
+
* Revisión): su ref es la foto del inicio. Sin puerto se contesta `sinBase` —el editor funciona
|
|
3855
|
+
* igual, sin marcas, y lo dice— en vez de dejar la barra en «Trayendo la base…».
|
|
3856
|
+
*/
|
|
3857
|
+
const atenderBaseDeFichero = async (ruta, base) => {
|
|
3858
|
+
const abierto = vestibulo.proyectoAbierto();
|
|
3859
|
+
if (abierto === undefined) {
|
|
3860
|
+
// Contesta igual: callar dejaría la barra del editor en «Trayendo la base…».
|
|
3861
|
+
emitir({ clase: "baseDeFichero", ruta, base, sinBase: "no hay ningún proyecto abierto" });
|
|
3862
|
+
return;
|
|
3863
|
+
}
|
|
3864
|
+
if (opciones.baseDeFichero === undefined) {
|
|
3865
|
+
emitir({ clase: "baseDeFichero", ruta, base, sinBase: "esta ejecución no puede leer git" });
|
|
3866
|
+
return;
|
|
3867
|
+
}
|
|
3868
|
+
try {
|
|
3869
|
+
emitir({ clase: "baseDeFichero", ...(await opciones.baseDeFichero(abierto.raiz, abierto.sesion, ruta, base)) });
|
|
3870
|
+
}
|
|
3871
|
+
catch (error) {
|
|
3872
|
+
informar(`no se pudo leer la base de «${ruta}» (${codigoDe(error)})`);
|
|
3873
|
+
emitir({ clase: "baseDeFichero", ruta, base, sinBase: "no se pudo leer la base" });
|
|
3874
|
+
}
|
|
3875
|
+
};
|
|
3761
3876
|
/**
|
|
3762
3877
|
* El contenido de un ARTEFACTO de la sesión abierta, por su nombre.
|
|
3763
3878
|
*
|
|
@@ -4645,6 +4760,63 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
4645
4760
|
});
|
|
4646
4761
|
respuesta.end(datos);
|
|
4647
4762
|
});
|
|
4763
|
+
/**
|
|
4764
|
+
* `GET RUTA_ICONO_DEL_PROYECTO?id=<id>` — el icono de la app de un proyecto BAJADO, para su fila
|
|
4765
|
+
* de la barra y la cabecera de su panel.
|
|
4766
|
+
*
|
|
4767
|
+
* Del cable llega el ID, nunca una ruta: el nombre sale del listado del SERVIDOR (como al borrar
|
|
4768
|
+
* la copia local), la raíz de `vestibulo.raizDeProyecto`, y el fichero lo decide `app.ini` con la
|
|
4769
|
+
* barrera de Ficheros (`iconoDelProyecto`). Se lee luego con `leerFichero`, que la vuelve a
|
|
4770
|
+
* aplicar. A diferencia de `RUTA_IMAGEN_DEL_PROYECTO`, vale para cualquier copia, no solo la
|
|
4771
|
+
* abierta: la barra enseña todas. Mismas cabeceras de seguridad (un `.svg` no ejecuta nada).
|
|
4772
|
+
*/
|
|
4773
|
+
servidor.registrarRuta("GET", RUTA_ICONO_DEL_PROYECTO, async (peticion, respuesta) => {
|
|
4774
|
+
const responder = (codigo, texto) => {
|
|
4775
|
+
respuesta.writeHead(codigo, { "Content-Type": "text/plain; charset=utf-8" });
|
|
4776
|
+
respuesta.end(texto);
|
|
4777
|
+
};
|
|
4778
|
+
const id = new URLSearchParams((peticion.url ?? "").split("?")[1] ?? "").get("id");
|
|
4779
|
+
if (id === null || id === "") {
|
|
4780
|
+
responder(400, "falta el proyecto");
|
|
4781
|
+
return;
|
|
4782
|
+
}
|
|
4783
|
+
const entorno = entornoElegido;
|
|
4784
|
+
const proyecto = proyectos.find((p) => p.id === id);
|
|
4785
|
+
const buscar = opciones.iconoDelProyecto;
|
|
4786
|
+
const leer = opciones.leerFichero;
|
|
4787
|
+
if (entorno === undefined || proyecto === undefined || buscar === undefined || leer === undefined || !hayCopiaLocal(proyecto.nombre)) {
|
|
4788
|
+
responder(404, "no hay icono");
|
|
4789
|
+
return;
|
|
4790
|
+
}
|
|
4791
|
+
let leido;
|
|
4792
|
+
try {
|
|
4793
|
+
const raiz = vestibulo.raizDeProyecto(entorno, proyecto.nombre);
|
|
4794
|
+
const ruta = await buscar(raiz);
|
|
4795
|
+
if (ruta === undefined) {
|
|
4796
|
+
responder(404, "no hay icono");
|
|
4797
|
+
return;
|
|
4798
|
+
}
|
|
4799
|
+
leido = await leer(raiz, ruta);
|
|
4800
|
+
}
|
|
4801
|
+
catch (error) {
|
|
4802
|
+
informar(`no se pudo servir el icono de «${proyecto.nombre}» (${codigoDe(error)})`);
|
|
4803
|
+
responder(500, "no se pudo leer");
|
|
4804
|
+
return;
|
|
4805
|
+
}
|
|
4806
|
+
if (leido.error !== undefined || leido.base64 === undefined || leido.mime === undefined) {
|
|
4807
|
+
responder(404, "no hay icono");
|
|
4808
|
+
return;
|
|
4809
|
+
}
|
|
4810
|
+
const datos = Buffer.from(leido.base64, "base64");
|
|
4811
|
+
respuesta.writeHead(200, {
|
|
4812
|
+
"Content-Type": leido.mime,
|
|
4813
|
+
"Content-Length": datos.length,
|
|
4814
|
+
"Content-Security-Policy": "sandbox",
|
|
4815
|
+
"X-Content-Type-Options": "nosniff",
|
|
4816
|
+
"Cache-Control": "no-store",
|
|
4817
|
+
});
|
|
4818
|
+
respuesta.end(datos);
|
|
4819
|
+
});
|
|
4648
4820
|
/**
|
|
4649
4821
|
* `GET RUTA_CALLBACK_MCP` — a donde vuelve el navegador tras autorizar un conector.
|
|
4650
4822
|
*
|
|
@@ -5124,6 +5296,13 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
5124
5296
|
respuesta.end();
|
|
5125
5297
|
return;
|
|
5126
5298
|
}
|
|
5299
|
+
if (typeof mensaje === "object" && mensaje !== null && mensaje.clase === "pendientesDeSubida") {
|
|
5300
|
+
const ruta = mensaje.ruta;
|
|
5301
|
+
void atenderPendientesDeSubida(typeof ruta === "string" ? ruta : undefined).catch(contar);
|
|
5302
|
+
respuesta.writeHead(204);
|
|
5303
|
+
respuesta.end();
|
|
5304
|
+
return;
|
|
5305
|
+
}
|
|
5127
5306
|
if (typeof mensaje === "object" && mensaje !== null && mensaje.clase === "revision") {
|
|
5128
5307
|
void atenderRevision(mensaje.ruta).catch(contar);
|
|
5129
5308
|
respuesta.writeHead(204);
|
|
@@ -5190,6 +5369,29 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
5190
5369
|
respuesta.end();
|
|
5191
5370
|
return;
|
|
5192
5371
|
}
|
|
5372
|
+
if (typeof mensaje === "object" &&
|
|
5373
|
+
mensaje !== null &&
|
|
5374
|
+
mensaje.clase === "guardarFichero" &&
|
|
5375
|
+
typeof mensaje.ruta === "string" &&
|
|
5376
|
+
typeof mensaje.texto === "string" &&
|
|
5377
|
+
typeof mensaje.huella === "string" &&
|
|
5378
|
+
typeof mensaje.id === "string" &&
|
|
5379
|
+
typeof mensaje.proyecto === "string") {
|
|
5380
|
+
void atenderGuardarFichero(mensaje.ruta, mensaje.texto, mensaje.huella, mensaje.id, mensaje.proyecto).catch(contar);
|
|
5381
|
+
respuesta.writeHead(204);
|
|
5382
|
+
respuesta.end();
|
|
5383
|
+
return;
|
|
5384
|
+
}
|
|
5385
|
+
if (typeof mensaje === "object" &&
|
|
5386
|
+
mensaje !== null &&
|
|
5387
|
+
mensaje.clase === "baseDeFichero" &&
|
|
5388
|
+
typeof mensaje.ruta === "string" &&
|
|
5389
|
+
(mensaje.base === "sesion" || mensaje.base === "commit")) {
|
|
5390
|
+
void atenderBaseDeFichero(mensaje.ruta, mensaje.base).catch(contar);
|
|
5391
|
+
respuesta.writeHead(204);
|
|
5392
|
+
respuesta.end();
|
|
5393
|
+
return;
|
|
5394
|
+
}
|
|
5193
5395
|
if (typeof mensaje === "object" && mensaje !== null && mensaje.clase === "artefacto" && typeof mensaje.nombre === "string") {
|
|
5194
5396
|
void atenderArtefacto(mensaje.nombre).catch(contar);
|
|
5195
5397
|
respuesta.writeHead(204);
|
|
@@ -6008,6 +6210,88 @@ export async function lecturaDeSync(raiz, sesion) {
|
|
|
6008
6210
|
};
|
|
6009
6211
|
}
|
|
6010
6212
|
}
|
|
6213
|
+
/**
|
|
6214
|
+
* TODO lo pendiente de subir del proyecto, como mensaje del cable (la pestaña Sincronización del
|
|
6215
|
+
* panel). Sin CloudStudio, o si la medida falla, lo DICE: «no se pudo medir» no es «nada».
|
|
6216
|
+
*/
|
|
6217
|
+
export async function lecturaDePendientesDeSubida(raiz) {
|
|
6218
|
+
const rama = cloudstudioDelProyecto(raiz)?.rama;
|
|
6219
|
+
if (rama === undefined || rama === "") {
|
|
6220
|
+
return { clase: "pendientesDeSubida", error: "este proyecto no está dado de alta en CloudStudio" };
|
|
6221
|
+
}
|
|
6222
|
+
try {
|
|
6223
|
+
return { clase: "pendientesDeSubida", ficheros: await pendientesDeSubida(raiz, rama) };
|
|
6224
|
+
}
|
|
6225
|
+
catch {
|
|
6226
|
+
return {
|
|
6227
|
+
clase: "pendientesDeSubida",
|
|
6228
|
+
error: "no se pudo medir lo que falta por subir: la copia local no se puede comparar con la rama",
|
|
6229
|
+
};
|
|
6230
|
+
}
|
|
6231
|
+
}
|
|
6232
|
+
/**
|
|
6233
|
+
* El diff de UN fichero pendiente, con las DOS guardas antes de tocar git:
|
|
6234
|
+
* - la ruta tiene que estar en la lista pendiente RECIÉN medida —una ruta inventada por el
|
|
6235
|
+
* cable no saca nada del disco—;
|
|
6236
|
+
* - y tiene que poder ENSEÑARSE con la MISMA regla del lector que Ficheros y Revisión
|
|
6237
|
+
* (`motivoParaNoEnsenar`, texto y `realpath`): un `.env` pendiente sale en la lista, pero su
|
|
6238
|
+
* diff NO viaja nunca.
|
|
6239
|
+
*/
|
|
6240
|
+
export async function lecturaDeParcheDeSubida(raiz, ruta) {
|
|
6241
|
+
const vacio = (negado) => ({
|
|
6242
|
+
clase: "parcheDeSubida",
|
|
6243
|
+
ruta,
|
|
6244
|
+
texto: "",
|
|
6245
|
+
recortado: false,
|
|
6246
|
+
...(negado === undefined ? {} : { negado }),
|
|
6247
|
+
});
|
|
6248
|
+
const rama = cloudstudioDelProyecto(raiz)?.rama;
|
|
6249
|
+
if (rama === undefined || rama === "")
|
|
6250
|
+
return vacio("el proyecto no está dado de alta en CloudStudio");
|
|
6251
|
+
let fichero;
|
|
6252
|
+
try {
|
|
6253
|
+
fichero = (await pendientesDeSubida(raiz, rama)).find((f) => f.ruta === ruta);
|
|
6254
|
+
}
|
|
6255
|
+
catch {
|
|
6256
|
+
return vacio("no se pudo medir lo que falta por subir");
|
|
6257
|
+
}
|
|
6258
|
+
if (fichero === undefined)
|
|
6259
|
+
return vacio("ya no está pendiente de subir");
|
|
6260
|
+
const motivo = await motivoParaNoEnsenar(raiz, ruta);
|
|
6261
|
+
if (motivo !== undefined)
|
|
6262
|
+
return vacio(motivo);
|
|
6263
|
+
const parche = await parcheDeSubida(raiz, rama, ruta, fichero.sinCommitear === true);
|
|
6264
|
+
return parche === undefined ? vacio() : { clase: "parcheDeSubida", ruta, ...parche };
|
|
6265
|
+
}
|
|
6266
|
+
/**
|
|
6267
|
+
* Marca cada fichero de la sesión con si sigue PENDIENTE de subir a CloudStudio.
|
|
6268
|
+
*
|
|
6269
|
+
* Revisión enseña solo lo de la sesión que falta por subir: lo ya subido no es nada que
|
|
6270
|
+
* revisar antes de subir. Se decide aquí y no en el cliente porque la lista (`revision`) y la
|
|
6271
|
+
* medida (`sync`) son dos mensajes que llegan en momentos distintos, y cruzar uno nuevo con
|
|
6272
|
+
* uno viejo escondería justo el fichero que el agente acaba de escribir —sin un solo error—.
|
|
6273
|
+
*
|
|
6274
|
+
* Misma medida que la banda (`cambiosPendientes` contra la ref de la bajada) y mismo cruce
|
|
6275
|
+
* por ruta exacta que `cuantosDeLaSesion`, **más lo que git aún no sigue** (`sinSeguir`): un
|
|
6276
|
+
* fichero recién creado a mitad de turno no sale en `git diff` y quedaría escondido justo
|
|
6277
|
+
* cuando más falta hace verlo. La regla: lo que la ref no confirma como subido, se enseña. **Sin CloudStudio, o si la medida falla, los
|
|
6278
|
+
* ficheros vuelven SIN la marca**: «no consta» no es «nada pendiente», y Revisión enseña
|
|
6279
|
+
* entonces todo. No va dentro de `cambiosDeSesion`, que usan también el comentario de cierre,
|
|
6280
|
+
* Soporte y `cuantosDeLaSesion`.
|
|
6281
|
+
*/
|
|
6282
|
+
export async function marcarPendientes(raiz, ficheros) {
|
|
6283
|
+
const rama = cloudstudioDelProyecto(raiz)?.rama;
|
|
6284
|
+
if (rama === undefined || rama === "" || ficheros.length === 0)
|
|
6285
|
+
return [...ficheros];
|
|
6286
|
+
try {
|
|
6287
|
+
const [cambios, nuevos] = await Promise.all([cambiosPendientes(raiz, rama), sinSeguir(raiz)]);
|
|
6288
|
+
const pendientes = new Set([...cambios.map((cambio) => cambio.ruta), ...nuevos]);
|
|
6289
|
+
return ficheros.map((fichero) => ({ ...fichero, pendiente: pendientes.has(fichero.ruta) }));
|
|
6290
|
+
}
|
|
6291
|
+
catch {
|
|
6292
|
+
return [...ficheros];
|
|
6293
|
+
}
|
|
6294
|
+
}
|
|
6011
6295
|
/**
|
|
6012
6296
|
* De los pendientes, cuántos tocó ESTA sesión — o `undefined` cuando no se puede atribuir.
|
|
6013
6297
|
*
|
|
@@ -6183,6 +6467,15 @@ export function ajusteDeWorkspaceCableado(opciones) {
|
|
|
6183
6467
|
},
|
|
6184
6468
|
};
|
|
6185
6469
|
}
|
|
6470
|
+
/**
|
|
6471
|
+
* Los tres puertos de la pestaña Ficheros —leer, guardar y la base de las marcas—, cableados con las
|
|
6472
|
+
* funciones de verdad y extraídos por el MISMO motivo que `emuladoresCableados`: dentro de
|
|
6473
|
+
* `arrancarConsolaWeb` serían un literal que ningún test mira, y cada uno es un campo OPCIONAL de
|
|
6474
|
+
* `montarRutas`, así que olvidarlo compila y pasa.
|
|
6475
|
+
*/
|
|
6476
|
+
export function ficherosCableados() {
|
|
6477
|
+
return { leerFichero: leerFicheroDeProyecto, escribirFichero: escribirFicheroDeProyecto, baseDeFichero, iconoDelProyecto };
|
|
6478
|
+
}
|
|
6186
6479
|
/**
|
|
6187
6480
|
* Los siete puertos de los emuladores, cableados con las funciones de verdad — extraídos por el
|
|
6188
6481
|
* MISMO motivo que `ajusteDeWorkspaceCableado`: dentro de `arrancarConsolaWeb` serían un literal
|
|
@@ -6704,7 +6997,8 @@ export async function arrancarConsolaWeb(opciones) {
|
|
|
6704
6997
|
modeloDelCambio,
|
|
6705
6998
|
planesDelProyecto,
|
|
6706
6999
|
visorOpenui: () => leerVisorOpenui(raizDelCliente),
|
|
6707
|
-
|
|
7000
|
+
// Leer, guardar y la base del editor, compuestos fuera para que la composición tenga test.
|
|
7001
|
+
...ficherosCableados(),
|
|
6708
7002
|
leerArtefacto: leerArtefactoDeSesion,
|
|
6709
7003
|
leerArtefactoCrudo,
|
|
6710
7004
|
correrPasoDeReceta: (receta, paso, alSalirLinea) => correrPasoDeReceta(receta, paso, { alSalirLinea }),
|