xonecode 0.20.3 → 0.22.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/apps/web/dist/assets/index-5rtynJV_.js +490 -0
- package/apps/web/dist/assets/index-Cq6c6WEF.css +1 -0
- package/apps/web/dist/assets/{php-CDg2Quc0.js → php-BTFUgUDo.js} +1 -1
- package/apps/web/dist/assets/{ruby-DlFaAWQT.js → ruby-BKKiBtJK.js} +1 -1
- package/apps/web/dist/index.html +2 -2
- package/dist/agent/conectores/servicioDeConectores.js +86 -45
- package/dist/agent/conectores/servicioDeConectores.js.map +1 -1
- package/dist/agent/config/modelos.js +4 -0
- package/dist/agent/config/modelos.js.map +1 -1
- package/dist/agent/config/mudanzaEnDisco.js +237 -3
- package/dist/agent/config/mudanzaEnDisco.js.map +1 -1
- package/dist/agent/config/selectorEnMaquina.js +3 -2
- package/dist/agent/config/selectorEnMaquina.js.map +1 -1
- package/dist/agent/config/settingsEnDisco.js +76 -2
- package/dist/agent/config/settingsEnDisco.js.map +1 -1
- package/dist/agent/dispositivos/arranqueDeEmulador.js +27 -3
- package/dist/agent/dispositivos/arranqueDeEmulador.js.map +1 -1
- package/dist/agent/dispositivos/instalacionEnMaquina.js +240 -29
- package/dist/agent/dispositivos/instalacionEnMaquina.js.map +1 -1
- package/dist/agent/dispositivos/lanzamientoEnMaquina.js +12 -17
- package/dist/agent/dispositivos/lanzamientoEnMaquina.js.map +1 -1
- package/dist/agent/grafo/proyecto.js +91 -50
- package/dist/agent/grafo/proyecto.js.map +1 -1
- package/dist/agent/motores/trueforge/capacidades.js +22 -0
- package/dist/agent/motores/trueforge/capacidades.js.map +1 -1
- package/dist/agent/motores/trueforge/medidaAutomatica.js +16 -3
- package/dist/agent/motores/trueforge/medidaAutomatica.js.map +1 -1
- package/dist/agent/motores/trueforge/modeloLangchain.js +7 -1
- package/dist/agent/motores/trueforge/modeloLangchain.js.map +1 -1
- package/dist/agent/motores/trueforge/sesionTrueforge.js +135 -14
- package/dist/agent/motores/trueforge/sesionTrueforge.js.map +1 -1
- package/dist/agent/motores/trueforge/toolsDeConectores.js +128 -0
- package/dist/agent/motores/trueforge/toolsDeConectores.js.map +1 -0
- package/dist/agent/motores/trueforge/traerDeStitch.js +142 -0
- package/dist/agent/motores/trueforge/traerDeStitch.js.map +1 -0
- package/dist/agent/subagentes/consumoExterno.js +15 -8
- package/dist/agent/subagentes/consumoExterno.js.map +1 -1
- package/dist/agent/subagentes/escrituraExterna.js +69 -2
- package/dist/agent/subagentes/escrituraExterna.js.map +1 -1
- package/dist/agent/subagentes/subagenteExterno.js +17 -1
- package/dist/agent/subagentes/subagenteExterno.js.map +1 -1
- package/dist/agent/turno/turnoReal.js +1 -0
- package/dist/agent/turno/turnoReal.js.map +1 -1
- package/dist/cli/main.js +5 -2
- package/dist/cli/main.js.map +1 -1
- package/dist/core/actos.js +44 -1
- package/dist/core/actos.js.map +1 -1
- package/dist/core/agentes.js +13 -2
- package/dist/core/agentes.js.map +1 -1
- package/dist/core/comandoExterno.js +69 -0
- package/dist/core/comandoExterno.js.map +1 -0
- package/dist/core/conectores.js +148 -3
- package/dist/core/conectores.js.map +1 -1
- package/dist/core/copiaDeAvd.js +111 -0
- package/dist/core/copiaDeAvd.js.map +1 -0
- package/dist/core/mudanzaDeWorkspace.js +105 -15
- package/dist/core/mudanzaDeWorkspace.js.map +1 -1
- package/dist/core/ports.js +28 -0
- package/dist/core/ports.js.map +1 -1
- package/dist/core/puertosDeAvd.js +64 -0
- package/dist/core/puertosDeAvd.js.map +1 -0
- package/dist/core/selectorDeCarpeta.js +88 -9
- package/dist/core/selectorDeCarpeta.js.map +1 -1
- package/dist/core/settings.js +60 -1
- package/dist/core/settings.js.map +1 -1
- package/dist/core/shellDeAgente.js +11 -0
- package/dist/core/shellDeAgente.js.map +1 -1
- package/dist/core/stitch.js +114 -0
- package/dist/core/stitch.js.map +1 -0
- package/dist/web/servidor/arranque.js +523 -52
- package/dist/web/servidor/arranque.js.map +1 -1
- package/dist/web/servidor/consolaDeTarea.js +4 -1
- package/dist/web/servidor/consolaDeTarea.js.map +1 -1
- package/dist/web/servidor/pielWeb.js +3 -1
- package/dist/web/servidor/pielWeb.js.map +1 -1
- package/dist/web/servidor/transporte.js.map +1 -1
- package/dist/web/servidor/vestibulo.js +154 -48
- package/dist/web/servidor/vestibulo.js.map +1 -1
- package/package.json +1 -1
- package/skills/xone-hotswap/SKILL.md +26 -10
- package/skills/xone-hotswap/lib/dispositivo.mjs +97 -3
- package/skills/xone-hotswap/references/conexion-y-despliegue.md +27 -15
- package/skills/xone-hotswap/scripts/xone-arrancar-android +9 -2
- package/skills/xone-hotswap/scripts/xone-desplegar-android +12 -6
- package/skills/xone-hotswap/scripts/xone-hotswap +32 -10
- package/skills/xone-hotswap/scripts/xone-reiniciar-android +23 -7
- package/apps/web/dist/assets/index-40sIfNsT.js +0 -485
- package/apps/web/dist/assets/index-BYTna8Os.css +0 -1
|
@@ -53,8 +53,9 @@ import { lanzarEnDispositivo, } from "../../agent/dispositivos/lanzamientoEnMaqu
|
|
|
53
53
|
import { motivoDeBloqueo, puedeLanzarse, } from "../../core/puedeLanzarse.js";
|
|
54
54
|
import { PLATAFORMAS_DE_DISPOSITIVO } from "../../core/settings.js";
|
|
55
55
|
import { instalarHerramientaDeDispositivos, verificarDispositivo } from "../../agent/dispositivos/dispositivosEnMaquina.js";
|
|
56
|
-
import { arrancarEmulador } from "../../agent/dispositivos/arranqueDeEmulador.js";
|
|
57
|
-
import { correrPasoDeReceta } from "../../agent/dispositivos/instalacionEnMaquina.js";
|
|
56
|
+
import { arrancarEmulador, pararEmulador } from "../../agent/dispositivos/arranqueDeEmulador.js";
|
|
57
|
+
import { borrarAvd, clonarAvd, correrPasoDeReceta, crearAvd } from "../../agent/dispositivos/instalacionEnMaquina.js";
|
|
58
|
+
import { asignarPuertosPendientes, motivoDeNombreDeAvdInaceptable, motivoDePuertoInaceptable, puertoDeAvd, } from "../../core/puertosDeAvd.js";
|
|
58
59
|
import { abrirCarpetaDelSistema, abrirDirectorioDelSistema } from "../../agent/config/selectorEnMaquina.js";
|
|
59
60
|
import { modelosDeMotor } from "../../agent/config/modelosDeMotor.js";
|
|
60
61
|
import { parsear, compatibleConOpenAi, esProveedorPersonalizado, idDeProveedorPersonalizado, motivoDeEndpointInaceptable, motivoDeSlugInaceptable, nombreDeProveedor, PROVEEDORES, slugDesdeNombre, resolver, SIN_CREDENCIAL, PAPELES, } from "../../core/modelos.js";
|
|
@@ -65,10 +66,10 @@ import { borrarCredencial, guardarCredencial } from "../../agent/config/authEnDi
|
|
|
65
66
|
import { borrarProveedorPersonalizado, guardarProveedorPersonalizado, proveedoresPersonalizados, } from "../../agent/config/configEnDisco.js";
|
|
66
67
|
import { crearCheckpointerDeProyecto, mantenimientoDelCheckpointer, } from "../../agent/sesiones/checkpointer.js";
|
|
67
68
|
import { resumenDePoda } from "../../core/podaDeCheckpoint.js";
|
|
68
|
-
import { cargarSettings, guardarConcurrenciaDeTareas, guardarDepurar, guardarWorkspace as guardarWorkspaceEnDisco, guardarDispositivos, guardarEntorno as guardarEntornoEnDisco, olvidarEntornoDeSettings, copiasDeEntorno, borrarCopiasDeEntorno, } from "../../agent/config/settingsEnDisco.js";
|
|
69
|
+
import { cargarSettings, guardarConcurrenciaDeTareas, guardarDepurar, guardarWorkspace as guardarWorkspaceEnDisco, guardarDispositivos, guardarAjusteDeAvd, quitarAjustesDeAvd, guardarPuertosAsignados, guardarEntorno as guardarEntornoEnDisco, olvidarEntornoDeSettings, copiasDeEntorno, borrarCopiasDeEntorno, } from "../../agent/config/settingsEnDisco.js";
|
|
69
70
|
import { dentroDelWorkspace, depuracionActiva, expandirConCasa, mismaRuta, motivoParaNoOlvidarEntorno, motivoDeNombreDeEntornoInaceptable, motivoDeWorkspaceInaceptable, } from "../../core/settings.js";
|
|
70
71
|
import { esModoDeEscritura, MODO_POR_OMISION, } from "../../core/modoDeEscritura.js";
|
|
71
|
-
import { mudarWorkspaceLegado } from "../../agent/config/mudanzaEnDisco.js";
|
|
72
|
+
import { megasDeLasMudanzas, mudarWorkspaceLegado } from "../../agent/config/mudanzaEnDisco.js";
|
|
72
73
|
import { elegirCarpetaEnMaquina, haySelectorDeCarpeta } from "../../agent/config/selectorEnMaquina.js";
|
|
73
74
|
import { cloudstudioDelProyecto } from "../../agent/config/configEnDisco.js";
|
|
74
75
|
import { abrirEnSistema, olvidarEntorno as olvidarCredencialesDeEntorno, rutaAuthPorDefecto } from "../../agent/cloudstudio/cloudstudioMcp.js";
|
|
@@ -107,7 +108,7 @@ import { baseDeWorkspaceLegada, baseDeWorkspacePorOmision, conexionDeVestibulo,
|
|
|
107
108
|
// doblan.
|
|
108
109
|
import { filaDeTarea } from "./transporte.js";
|
|
109
110
|
import { esEsfuerzo, nivelesDeEsfuerzo } from "../../core/esfuerzo.js";
|
|
110
|
-
import { CATALOGO_DE_CONECTORES, definicionDelCable, RUTA_CALLBACK_MCP } from "../../core/conectores.js";
|
|
111
|
+
import { CATALOGO_DE_CONECTORES, conectoresParaElAgente, definicionDelCable, RUTA_CALLBACK_MCP } from "../../core/conectores.js";
|
|
111
112
|
import { servicioDeConectoresCableado } from "../../agent/conectores/servicioDeConectores.js";
|
|
112
113
|
import { crearGestorJira } from "../../agent/conectores/gestorJira.js";
|
|
113
114
|
import { crearGestorNotion } from "../../agent/conectores/gestorNotion.js";
|
|
@@ -1020,6 +1021,14 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
1020
1021
|
})),
|
|
1021
1022
|
};
|
|
1022
1023
|
};
|
|
1024
|
+
/**
|
|
1025
|
+
* El último progreso de la mudanza del workspace EN MARCHA, o ausente. Existe para la ráfaga:
|
|
1026
|
+
* un navegador que reconecta a mitad de una copia larga tira la mudanza al caerse el cable
|
|
1027
|
+
* (`store.ts#marcarDesconectado`) y el `resultado` solo va a quien está conectado al acabar,
|
|
1028
|
+
* así que sin esto no sabría que sigue una —y los controles del workspace se le quedarían
|
|
1029
|
+
* sueltos mientras el servidor niega cualquier otro cambio—.
|
|
1030
|
+
*/
|
|
1031
|
+
let ultimoProgresoDeMudanza;
|
|
1023
1032
|
/**
|
|
1024
1033
|
* Engancha el cable a la consola que toque, con el transcript entero por delante.
|
|
1025
1034
|
*
|
|
@@ -1114,6 +1123,8 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
1114
1123
|
cliente(tareas);
|
|
1115
1124
|
if (workspace !== undefined)
|
|
1116
1125
|
cliente(workspace);
|
|
1126
|
+
if (ultimoProgresoDeMudanza !== undefined)
|
|
1127
|
+
cliente({ clase: "mudanzaDeWorkspace", progreso: ultimoProgresoDeMudanza });
|
|
1117
1128
|
if (depuracion !== undefined)
|
|
1118
1129
|
cliente(depuracion);
|
|
1119
1130
|
if (conectores !== undefined)
|
|
@@ -1124,12 +1135,13 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
1124
1135
|
modelo: consumoDeLaSesion.modelo,
|
|
1125
1136
|
externo: consumoDeLaSesion.externo,
|
|
1126
1137
|
ventana: ventanaDeAhora(consumoDeLaSesion.contexto),
|
|
1138
|
+
...(consumoDeLaSesion.porModelo === undefined ? {} : { porModelo: consumoDeLaSesion.porModelo }),
|
|
1127
1139
|
});
|
|
1128
1140
|
}
|
|
1129
1141
|
// La foto de la máquina, si ya se tomó. Si no, se dispara abajo UNA vez y llega a
|
|
1130
1142
|
// todos por el SSE cuando termine: no se espera aquí, que son varios procesos.
|
|
1131
1143
|
if (informeDeDispositivos !== undefined) {
|
|
1132
|
-
cliente(
|
|
1144
|
+
cliente(mensajeDeDispositivos(informeDeDispositivos));
|
|
1133
1145
|
}
|
|
1134
1146
|
// Y si hay turno corriendo, se dice: quien conecta a mitad no vio el mensaje que lo
|
|
1135
1147
|
// anunció, y sin esto vería el compositor encendido y sin borde —«no pasa nada»—
|
|
@@ -1274,6 +1286,8 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
1274
1286
|
? { clase: "workspace", ruta }
|
|
1275
1287
|
: { clase: "workspace", ruta, puedeElegir: true };
|
|
1276
1288
|
};
|
|
1289
|
+
/** Hay una mudanza del workspace en marcha: una segunda se niega, y crear una tarea también. */
|
|
1290
|
+
let mudandoWorkspace = false;
|
|
1277
1291
|
const emitirWorkspace = () => {
|
|
1278
1292
|
const m = mensajeDeWorkspace();
|
|
1279
1293
|
if (m !== undefined)
|
|
@@ -1662,6 +1676,30 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
1662
1676
|
};
|
|
1663
1677
|
/** Los ajustes de destino, o `{}` —que es «se miran todos»— si esta ejecución no los lee. */
|
|
1664
1678
|
const ajustesDeDispositivos = () => opciones.ajustesDeDispositivos?.() ?? {};
|
|
1679
|
+
/**
|
|
1680
|
+
* Qué dispositivo tiene elegido CADA consola abierta, no solo la del foco: con dos proyectos
|
|
1681
|
+
* abiertos sobre dos emuladores, lo que la PASTILLA del chat tiene que poder decir es «este lo usa Tienda».
|
|
1682
|
+
*
|
|
1683
|
+
* Se pregunta a las consolas vivas y no se guarda, igual que `sesionesTrabajando`: es un
|
|
1684
|
+
* estado de este instante. El `proyecto` es el SEGMENTO de su carpeta —el mismo nombre que ya
|
|
1685
|
+
* viaja en el lanzamiento (`medirLanzable`) y que enseña la barra—, nunca la ruta: `sinRutas`.
|
|
1686
|
+
* **Límite declarado**: va por `basename(raiz)`, así que el mismo nombre de proyecto en dos
|
|
1687
|
+
* entornos (`<entorno>/<proyecto>`) se confunde.
|
|
1688
|
+
*/
|
|
1689
|
+
const dispositivosEnUso = () => vestibulo
|
|
1690
|
+
.proyectosAbiertos()
|
|
1691
|
+
.flatMap((c) => (c.dispositivo === undefined ? [] : [{ id: c.dispositivo.id, proyecto: basename(c.raiz) }]));
|
|
1692
|
+
/**
|
|
1693
|
+
* El mensaje `dispositivos` entero, compuesto en UN sitio: la foto, los ajustes leídos AHORA y
|
|
1694
|
+
* quién usa qué. Con cada emisor montando el suyo, uno se quedaba sin el campo nuevo —una
|
|
1695
|
+
* pestaña que reconecta, o una verificación, borrarían el «en uso» de la pantalla—.
|
|
1696
|
+
*/
|
|
1697
|
+
const mensajeDeDispositivos = (informe) => ({
|
|
1698
|
+
clase: "dispositivos",
|
|
1699
|
+
informe,
|
|
1700
|
+
ajustes: ajustesDeDispositivos(),
|
|
1701
|
+
enUso: dispositivosEnUso(),
|
|
1702
|
+
});
|
|
1665
1703
|
let deteccionEnVuelo;
|
|
1666
1704
|
/**
|
|
1667
1705
|
* Cómo acabó el último «Arrancar». Viaja con la foto porque el resultado de arrancar ES la
|
|
@@ -1678,10 +1716,24 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
1678
1716
|
deteccionEnVuelo = (async () => {
|
|
1679
1717
|
try {
|
|
1680
1718
|
informeDeDispositivos = sinRutas(await detectar());
|
|
1719
|
+
// Cada AVD necesita SU puerto, y fijado —también el único, en silencio—: una propuesta que
|
|
1720
|
+
// se recalculara al crear otro AVD le movería el puerto a uno que ya se usa. Se siembra
|
|
1721
|
+
// ANTES de emitir para que la foto salga con los puertos ya puestos, y releyendo después
|
|
1722
|
+
// (`ajustesDeDispositivos` va al disco): lo emitido es lo que quedó escrito. Un AVD recién
|
|
1723
|
+
// creado desde Ajustes recibe el suyo aquí, en la medida que cierra su trabajo.
|
|
1724
|
+
const pendientes = asignarPuertosPendientes(informeDeDispositivos.avds, ajustesDeDispositivos());
|
|
1725
|
+
if (pendientes !== undefined && opciones.guardarPuertosAsignados !== undefined) {
|
|
1726
|
+
try {
|
|
1727
|
+
opciones.guardarPuertosAsignados(pendientes);
|
|
1728
|
+
}
|
|
1729
|
+
catch (error) {
|
|
1730
|
+
// No impide emitir: la foto sigue siendo verdad, y sin puerto guardado el AVD usa el
|
|
1731
|
+
// de fábrica, que es lo que había antes. Lo que no se puede es callarlo.
|
|
1732
|
+
informar(`no se pudieron guardar los puertos de los emuladores (${codigoDe(error)})`);
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1681
1735
|
emitir({
|
|
1682
|
-
|
|
1683
|
-
informe: informeDeDispositivos,
|
|
1684
|
-
ajustes: ajustesDeDispositivos(),
|
|
1736
|
+
...mensajeDeDispositivos(informeDeDispositivos),
|
|
1685
1737
|
...(ultimoArranque === undefined ? {} : { arranque: ultimoArranque }),
|
|
1686
1738
|
});
|
|
1687
1739
|
}
|
|
@@ -1732,7 +1784,9 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
1732
1784
|
}
|
|
1733
1785
|
arranqueEnVuelo = avd;
|
|
1734
1786
|
try {
|
|
1735
|
-
|
|
1787
|
+
// «Sin ventana» es un ajuste de ESE AVD (Ajustes → Dispositivos), leído al arrancar y no
|
|
1788
|
+
// del cliente: lo que se pulsa es «Arrancar», y cómo se arranca ya está elegido.
|
|
1789
|
+
const r = await arrancar(avd, { sinVentana: ajustesDeDispositivos().avds?.[avd]?.sinVentana === true });
|
|
1736
1790
|
ultimoArranque = { avd, ...r };
|
|
1737
1791
|
}
|
|
1738
1792
|
catch (error) {
|
|
@@ -1797,7 +1851,80 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
1797
1851
|
...informeDeDispositivos,
|
|
1798
1852
|
dispositivos: informeDeDispositivos.dispositivos.map((d) => (d.id === id ? { ...d, verificado } : d)),
|
|
1799
1853
|
};
|
|
1800
|
-
emitir(
|
|
1854
|
+
emitir(mensajeDeDispositivos(informeDeDispositivos));
|
|
1855
|
+
};
|
|
1856
|
+
/**
|
|
1857
|
+
* **Lo de UN AVD: su puerto del túnel y si arranca sin ventana.**
|
|
1858
|
+
*
|
|
1859
|
+
* El nombre llega por el cable, así que tiene que estar en NUESTRA última medida (la misma
|
|
1860
|
+
* guarda que «Arrancar»): guardar ajustes de un AVD que no existe llenaría `settings.json` de
|
|
1861
|
+
* nombres que nadie ve. El puerto pasa por `motivoDePuertoInaceptable` contra los ajustes de
|
|
1862
|
+
* AHORA —dos AVD con el mismo puerto son el fallo que todo esto viene a quitar—, y la negativa
|
|
1863
|
+
* se dice sin guardar NADA del mensaje, tampoco el `sinVentana` que viniera con él: medio
|
|
1864
|
+
* cambio aplicado es un cambio que nadie pidió.
|
|
1865
|
+
*
|
|
1866
|
+
* No se vuelve a medir: el ajuste no cambia la máquina. Se reemite la foto que hay con los
|
|
1867
|
+
* ajustes releídos, que es lo que el campo tiene que enseñar.
|
|
1868
|
+
*/
|
|
1869
|
+
const atenderAjusteDeAvd = async (avd, cambio) => {
|
|
1870
|
+
const guardar = opciones.guardarAjusteDeAvd;
|
|
1871
|
+
if (guardar === undefined) {
|
|
1872
|
+
informar("esta ejecución no puede guardar los ajustes de los emuladores");
|
|
1873
|
+
return;
|
|
1874
|
+
}
|
|
1875
|
+
if (informeDeDispositivos === undefined)
|
|
1876
|
+
await atenderDispositivos().catch(contar);
|
|
1877
|
+
if (informeDeDispositivos === undefined || !informeDeDispositivos.avds.includes(avd)) {
|
|
1878
|
+
informar(`no consta ningún AVD llamado «${avd}» en la última medida`);
|
|
1879
|
+
return;
|
|
1880
|
+
}
|
|
1881
|
+
if (cambio.puerto !== undefined) {
|
|
1882
|
+
const motivo = motivoDePuertoInaceptable(cambio.puerto, avd, ajustesDeDispositivos());
|
|
1883
|
+
if (motivo !== undefined) {
|
|
1884
|
+
informar(`no se cambió el puerto de ${avd}: ${motivo}`);
|
|
1885
|
+
return;
|
|
1886
|
+
}
|
|
1887
|
+
}
|
|
1888
|
+
try {
|
|
1889
|
+
guardar(avd, cambio);
|
|
1890
|
+
}
|
|
1891
|
+
catch (error) {
|
|
1892
|
+
informar(`no se pudieron guardar los ajustes de ${avd} (${codigoDe(error)})`);
|
|
1893
|
+
return;
|
|
1894
|
+
}
|
|
1895
|
+
emitir(mensajeDeDispositivos(informeDeDispositivos));
|
|
1896
|
+
};
|
|
1897
|
+
/**
|
|
1898
|
+
* **Parar un emulador**, por su serie de la última medida y solo si ES un emulador: la serie
|
|
1899
|
+
* acaba siendo un argumento de `adb`, y un físico no se «para» desde aquí. Al acabar se mide
|
|
1900
|
+
* SIEMPRE, como al arrancar: la foto es la que dice si se paró.
|
|
1901
|
+
*/
|
|
1902
|
+
const atenderParadaDeEmulador = async (id) => {
|
|
1903
|
+
const parar = opciones.pararEmulador;
|
|
1904
|
+
if (parar === undefined) {
|
|
1905
|
+
informar("esta ejecución no puede parar emuladores");
|
|
1906
|
+
return;
|
|
1907
|
+
}
|
|
1908
|
+
if (informeDeDispositivos === undefined)
|
|
1909
|
+
await atenderDispositivos().catch(contar);
|
|
1910
|
+
const dispositivo = informeDeDispositivos?.dispositivos.find((d) => d.id === id);
|
|
1911
|
+
if (dispositivo === undefined || dispositivo.clase !== "emulador") {
|
|
1912
|
+
informar(`no consta ningún emulador «${id}» en la última medida`);
|
|
1913
|
+
return;
|
|
1914
|
+
}
|
|
1915
|
+
try {
|
|
1916
|
+
const r = await parar(dispositivo.id);
|
|
1917
|
+
if (!r.ok)
|
|
1918
|
+
informar(`no se pudo parar ${dispositivo.nombre}: ${r.detalle}`);
|
|
1919
|
+
}
|
|
1920
|
+
catch (error) {
|
|
1921
|
+
// El puerto promete no lanzar; si lo hace, se cuenta y se mide igual.
|
|
1922
|
+
informar(`no se pudo parar ${dispositivo.nombre} (${codigoDe(error)})`);
|
|
1923
|
+
}
|
|
1924
|
+
finally {
|
|
1925
|
+
informeDeDispositivos = undefined;
|
|
1926
|
+
await atenderDispositivos().catch(contar);
|
|
1927
|
+
}
|
|
1801
1928
|
};
|
|
1802
1929
|
const atenderCatalogo = async (proveedor) => {
|
|
1803
1930
|
if (!proveedorConocido(proveedor))
|
|
@@ -2203,6 +2330,7 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
2203
2330
|
modelo: alAbrir?.modelo ?? SIN_CONSUMO,
|
|
2204
2331
|
externo: alAbrir?.externo ?? SIN_CONSUMO,
|
|
2205
2332
|
ventana: ventanaDeAhora(alAbrir?.contexto ?? 0),
|
|
2333
|
+
...(alAbrir?.porModelo === undefined ? {} : { porModelo: alAbrir.porModelo }),
|
|
2206
2334
|
});
|
|
2207
2335
|
// Y la cola se vuelve a mirar: este proyecto queda bloqueado para las tareas —gana la
|
|
2208
2336
|
// persona— y el que estuviera abierto antes acaba de quedar libre.
|
|
@@ -2925,7 +3053,17 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
2925
3053
|
// doble mal hecho, o un bug suyo en el camino síncrono— dejaría el recorrido en
|
|
2926
3054
|
// `corriendo` para siempre y el botón muerto, que es justo lo que este `try` existe para
|
|
2927
3055
|
// evitar. Su contrato es no lanzar nunca; esto es por si deja de cumplirlo.
|
|
2928
|
-
const enCurso = lanzar({
|
|
3056
|
+
const enCurso = lanzar({
|
|
3057
|
+
dispositivo: medida.dispositivo,
|
|
3058
|
+
raiz: medida.abierto.raiz,
|
|
3059
|
+
app: medida.app,
|
|
3060
|
+
// El puerto LOCAL del túnel es el de SU AVD: con dos emuladores y el mismo puerto,
|
|
3061
|
+
// el `adb forward` del segundo le quita el túnel al primero sin error. Un físico no
|
|
3062
|
+
// tiene AVD y va por el de fábrica, que es lo que hace el lanzador sin puerto.
|
|
3063
|
+
...(medida.dispositivo.avd === undefined
|
|
3064
|
+
? {}
|
|
3065
|
+
: { puerto: puertoDeAvd(ajustesDeDispositivos(), medida.dispositivo.avd) }),
|
|
3066
|
+
}, { alFase });
|
|
2929
3067
|
trabajo.cancelar = enCurso.cancelar;
|
|
2930
3068
|
const resultado = await enCurso.terminado;
|
|
2931
3069
|
// El último SIEMPRE se emite, aunque no haya pasado el plazo: es el que dice cómo acabó.
|
|
@@ -3673,40 +3811,25 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
3673
3811
|
ultimoProgreso = Date.now();
|
|
3674
3812
|
};
|
|
3675
3813
|
/**
|
|
3676
|
-
*
|
|
3814
|
+
* El cerrojo compartido: monta `trabajo`, emite el primer progreso y, al terminar, el último y
|
|
3815
|
+
* una medida nueva. Lo usan los pasos de receta y crear un AVD, que son la misma clase de
|
|
3816
|
+
* trabajo (`sdkmanager`/`avdmanager` sobre el mismo SDK): quien llama ya comprobó que no había
|
|
3817
|
+
* otro en marcha.
|
|
3677
3818
|
*
|
|
3678
|
-
* Al terminar se vuelve a MEDIR, y es lo que decide si el
|
|
3819
|
+
* Al terminar se vuelve a MEDIR, y es lo que decide si el trabajo queda hecho: lo que diga el
|
|
3679
3820
|
* instalador es lo que él cree, y la foto es lo que hay. Es la misma regla que ya sigue
|
|
3680
3821
|
* `instalar`.
|
|
3681
3822
|
*/
|
|
3682
|
-
const
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
emitirProgreso("corriendo");
|
|
3690
|
-
return;
|
|
3691
|
-
}
|
|
3692
|
-
const correr = opciones.correrPasoDeReceta;
|
|
3693
|
-
if (correr === undefined) {
|
|
3694
|
-
informar("esta ejecución no puede ejecutar pasos de instalación");
|
|
3695
|
-
return;
|
|
3696
|
-
}
|
|
3697
|
-
const enMarcha = correr(mensaje.id, mensaje.paso, (linea) => {
|
|
3698
|
-
if (trabajo === undefined)
|
|
3699
|
-
return;
|
|
3700
|
-
trabajo.lineas.push(linea);
|
|
3701
|
-
// A ritmo: cada emisión manda la cola entera, y por línea sería cuadrático en bytes
|
|
3702
|
-
// con un `sdkmanager` que habla cada pocos milisegundos. Es la misma razón que
|
|
3703
|
-
// `MS_ENTRE_PARCIALES` en la piel web.
|
|
3704
|
-
if (Date.now() - ultimoProgreso >= MS_ENTRE_PROGRESOS)
|
|
3705
|
-
emitirProgreso("corriendo");
|
|
3706
|
-
});
|
|
3823
|
+
const correrTrabajo = (receta, paso, enMarcha,
|
|
3824
|
+
/**
|
|
3825
|
+
* Lo que el servidor apunta al acabar (la procedencia de un AVD nuevo, los ajustes de uno
|
|
3826
|
+
* eliminado), ANTES del último progreso y de la medida: así la foto que sigue ya lo trae.
|
|
3827
|
+
* Un fallo suyo se dice y no tumba el cierre del trabajo.
|
|
3828
|
+
*/
|
|
3829
|
+
alAcabar) => {
|
|
3707
3830
|
trabajo = {
|
|
3708
|
-
receta
|
|
3709
|
-
paso
|
|
3831
|
+
receta,
|
|
3832
|
+
paso,
|
|
3710
3833
|
titulo: enMarcha.titulo,
|
|
3711
3834
|
lineas: [],
|
|
3712
3835
|
cancelar: enMarcha.cancelar,
|
|
@@ -3715,6 +3838,12 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
3715
3838
|
emitirProgreso("corriendo");
|
|
3716
3839
|
void (async () => {
|
|
3717
3840
|
const resultado = await enMarcha.terminado;
|
|
3841
|
+
try {
|
|
3842
|
+
alAcabar?.(resultado);
|
|
3843
|
+
}
|
|
3844
|
+
catch (error) {
|
|
3845
|
+
informar(`no se pudo apuntar lo que cambió al acabar (${codigoDe(error)})`);
|
|
3846
|
+
}
|
|
3718
3847
|
// El último progreso SIEMPRE se emite, aunque no haya pasado el plazo: es el que
|
|
3719
3848
|
// completa el log y el que dice cómo acabó.
|
|
3720
3849
|
ultimoProgreso = 0;
|
|
@@ -3725,6 +3854,145 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
3725
3854
|
await atenderDispositivos().catch(contar);
|
|
3726
3855
|
})();
|
|
3727
3856
|
};
|
|
3857
|
+
/**
|
|
3858
|
+
* Cada línea de la salida, a la cola del trabajo en curso y emitida A RITMO: cada emisión manda
|
|
3859
|
+
* la cola entera, y por línea sería cuadrático en bytes con un `sdkmanager` que habla cada
|
|
3860
|
+
* pocos milisegundos. Es la misma razón que `MS_ENTRE_PARCIALES` en la piel web.
|
|
3861
|
+
*/
|
|
3862
|
+
const alSalirLineaDelTrabajo = (linea) => {
|
|
3863
|
+
if (trabajo === undefined)
|
|
3864
|
+
return;
|
|
3865
|
+
trabajo.lineas.push(linea);
|
|
3866
|
+
if (Date.now() - ultimoProgreso >= MS_ENTRE_PROGRESOS)
|
|
3867
|
+
emitirProgreso("corriendo");
|
|
3868
|
+
};
|
|
3869
|
+
/** Lanza un paso, o cancela el que corre. */
|
|
3870
|
+
const atenderReceta = (mensaje) => {
|
|
3871
|
+
if (mensaje.accion === "cancelar") {
|
|
3872
|
+
trabajo?.cancelar();
|
|
3873
|
+
return;
|
|
3874
|
+
}
|
|
3875
|
+
if (trabajo !== undefined) {
|
|
3876
|
+
// Ya hay uno: se reenvía su estado en vez de lanzar otro.
|
|
3877
|
+
emitirProgreso("corriendo");
|
|
3878
|
+
return;
|
|
3879
|
+
}
|
|
3880
|
+
const correr = opciones.correrPasoDeReceta;
|
|
3881
|
+
if (correr === undefined) {
|
|
3882
|
+
informar("esta ejecución no puede ejecutar pasos de instalación");
|
|
3883
|
+
return;
|
|
3884
|
+
}
|
|
3885
|
+
correrTrabajo(mensaje.id, mensaje.paso, correr(mensaje.id, mensaje.paso, alSalirLineaDelTrabajo));
|
|
3886
|
+
};
|
|
3887
|
+
/** ¿Corre ahora el AVD `avd`, según la ÚLTIMA medida? Todo lo que no es «apagado» o «no disponible» cuenta. */
|
|
3888
|
+
const baseEnMarcha = (avd) => (informeDeDispositivos?.dispositivos ?? []).some((d) => d.avd === avd && d.estado !== "apagado" && d.estado !== "no-disponible");
|
|
3889
|
+
/**
|
|
3890
|
+
* **Crear un AVD con nombre**, por el MISMO cerrojo que las recetas y contado igual
|
|
3891
|
+
* (`receta: "crear-avd"`, `paso: 0`): la pantalla ya sabe pintar ese log.
|
|
3892
|
+
*
|
|
3893
|
+
* El nombre acaba siendo un argumento de `avdmanager`, así que se valida aquí con la regla
|
|
3894
|
+
* pura (forma, largo, y que no exista ya en la última medida) antes de lanzar nada; sin medida
|
|
3895
|
+
* no se puede saber si se repite, así que se mide primero. El cerrojo se vuelve a mirar
|
|
3896
|
+
* DESPUÉS de esa espera: mientras se medía pudo arrancar otro trabajo.
|
|
3897
|
+
*/
|
|
3898
|
+
const atenderCrearEmulador = async (nombre, base, conDatos) => {
|
|
3899
|
+
if (trabajo !== undefined) {
|
|
3900
|
+
emitirProgreso("corriendo");
|
|
3901
|
+
return;
|
|
3902
|
+
}
|
|
3903
|
+
const crear = opciones.crearAvd;
|
|
3904
|
+
if (crear === undefined) {
|
|
3905
|
+
informar("esta ejecución no puede crear emuladores");
|
|
3906
|
+
return;
|
|
3907
|
+
}
|
|
3908
|
+
if (informeDeDispositivos === undefined)
|
|
3909
|
+
await atenderDispositivos().catch(contar);
|
|
3910
|
+
if (informeDeDispositivos === undefined) {
|
|
3911
|
+
informar("no se creó el emulador: todavía no hay una medida con la que comprobar el nombre");
|
|
3912
|
+
return;
|
|
3913
|
+
}
|
|
3914
|
+
const motivo = motivoDeNombreDeAvdInaceptable(nombre, informeDeDispositivos.avds);
|
|
3915
|
+
if (motivo !== undefined) {
|
|
3916
|
+
informar(`no se creó el emulador: ${motivo}`);
|
|
3917
|
+
return;
|
|
3918
|
+
}
|
|
3919
|
+
if (base !== undefined) {
|
|
3920
|
+
// La base es un NOMBRE que acaba en una ruta: tiene que ser uno de los que la medida vio.
|
|
3921
|
+
if (!informeDeDispositivos.avds.includes(base)) {
|
|
3922
|
+
informar(`no se creó el emulador: ${base} no es un emulador que conozcamos`);
|
|
3923
|
+
return;
|
|
3924
|
+
}
|
|
3925
|
+
// Clonar copia discos que la base tiene bloqueados mientras corre (`multiinstance.lock`);
|
|
3926
|
+
// copiar solo la configuración no los toca y vale con ella encendida.
|
|
3927
|
+
if (conDatos === true && baseEnMarcha(base)) {
|
|
3928
|
+
informar(`no se creó el emulador: ${base} está en marcha; apágalo para clonarlo con lo instalado`);
|
|
3929
|
+
return;
|
|
3930
|
+
}
|
|
3931
|
+
}
|
|
3932
|
+
if (trabajo !== undefined) {
|
|
3933
|
+
emitirProgreso("corriendo");
|
|
3934
|
+
return;
|
|
3935
|
+
}
|
|
3936
|
+
correrTrabajo("crear-avd", 0, crear(nombre, alSalirLineaDelTrabajo, base === undefined ? undefined : { base, conDatos: conDatos === true }),
|
|
3937
|
+
// La procedencia se GUARDA al acabar bien: «copia de X» (configuración) o «clon de X» (con lo
|
|
3938
|
+
// instalado). Sin base no hay de quién, y un fallo no deja AVD al que apuntarla.
|
|
3939
|
+
base === undefined
|
|
3940
|
+
? undefined
|
|
3941
|
+
: (r) => {
|
|
3942
|
+
if (r.estado !== "ok")
|
|
3943
|
+
return;
|
|
3944
|
+
opciones.guardarAjusteDeAvd?.(nombre, { copiaDe: base, ...(conDatos === true ? { clon: true } : {}) });
|
|
3945
|
+
});
|
|
3946
|
+
};
|
|
3947
|
+
/**
|
|
3948
|
+
* **Eliminar un AVD**, por el MISMO cerrojo que crear (`receta: "borrar-avd"`, `paso: 0`). El
|
|
3949
|
+
* nombre llega por el cable y acaba en un argumento de `avdmanager`, así que tiene que ser uno de
|
|
3950
|
+
* los de NUESTRA última medida. Tres negativas, en orden, y cada una se DICE (la del navegador no
|
|
3951
|
+
* llega de vuelta: el cliente aplica las mismas reglas antes y no deja pulsar):
|
|
3952
|
+
* - nunca el ÚLTIMO: es la base de las copias, y siempre tiene que quedar uno;
|
|
3953
|
+
* - nunca uno EN MARCHA (el mismo criterio que `baseEnMarcha`): el emulador tiene sus discos
|
|
3954
|
+
* bloqueados y borrar la carpeta por debajo lo dejaría roto.
|
|
3955
|
+
* Al acabar BIEN se quitan también sus ajustes (puerto, sin ventana, procedencia) para que su
|
|
3956
|
+
* puerto quede libre, y `correrTrabajo` vuelve a medir.
|
|
3957
|
+
*/
|
|
3958
|
+
const atenderEliminarEmulador = async (avd) => {
|
|
3959
|
+
if (trabajo !== undefined) {
|
|
3960
|
+
emitirProgreso("corriendo");
|
|
3961
|
+
return;
|
|
3962
|
+
}
|
|
3963
|
+
const borrar = opciones.borrarAvd;
|
|
3964
|
+
if (borrar === undefined) {
|
|
3965
|
+
informar("esta ejecución no puede eliminar emuladores");
|
|
3966
|
+
return;
|
|
3967
|
+
}
|
|
3968
|
+
if (informeDeDispositivos === undefined)
|
|
3969
|
+
await atenderDispositivos().catch(contar);
|
|
3970
|
+
if (informeDeDispositivos === undefined) {
|
|
3971
|
+
informar("no se eliminó el emulador: todavía no hay una medida con la que comprobarlo");
|
|
3972
|
+
return;
|
|
3973
|
+
}
|
|
3974
|
+
if (!informeDeDispositivos.avds.includes(avd)) {
|
|
3975
|
+
informar(`no se eliminó el emulador: ${avd} no es un emulador que conozcamos`);
|
|
3976
|
+
return;
|
|
3977
|
+
}
|
|
3978
|
+
if (informeDeDispositivos.avds.length <= 1) {
|
|
3979
|
+
informar(`no se eliminó ${avd}: es el único: siempre tiene que quedar al menos uno`);
|
|
3980
|
+
return;
|
|
3981
|
+
}
|
|
3982
|
+
if (baseEnMarcha(avd)) {
|
|
3983
|
+
informar(`no se eliminó ${avd}: está en marcha; apágalo para eliminarlo`);
|
|
3984
|
+
return;
|
|
3985
|
+
}
|
|
3986
|
+
// Mientras se medía pudo arrancar otro trabajo.
|
|
3987
|
+
if (trabajo !== undefined) {
|
|
3988
|
+
emitirProgreso("corriendo");
|
|
3989
|
+
return;
|
|
3990
|
+
}
|
|
3991
|
+
correrTrabajo("borrar-avd", 0, borrar(avd, alSalirLineaDelTrabajo), (r) => {
|
|
3992
|
+
if (r.estado === "ok")
|
|
3993
|
+
opciones.quitarAjustesDeAvd?.(avd);
|
|
3994
|
+
});
|
|
3995
|
+
};
|
|
3728
3996
|
/**
|
|
3729
3997
|
* Los modelos de un motor externo, para el desplegable de un subagente.
|
|
3730
3998
|
*
|
|
@@ -3911,7 +4179,13 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
3911
4179
|
// contador a pintar un cero que nadie ha medido.
|
|
3912
4180
|
if (c === undefined)
|
|
3913
4181
|
return;
|
|
3914
|
-
emitir({
|
|
4182
|
+
emitir({
|
|
4183
|
+
clase: "consumo",
|
|
4184
|
+
modelo: c.modelo,
|
|
4185
|
+
externo: c.externo,
|
|
4186
|
+
ventana: ventanaDeAhora(c.contexto),
|
|
4187
|
+
...(c.porModelo === undefined ? {} : { porModelo: c.porModelo }),
|
|
4188
|
+
});
|
|
3915
4189
|
};
|
|
3916
4190
|
/**
|
|
3917
4191
|
* Cuánto ocupa la ventana y cuál es su tope, si se sabe.
|
|
@@ -4888,6 +5162,51 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
4888
5162
|
respuesta.end();
|
|
4889
5163
|
return;
|
|
4890
5164
|
}
|
|
5165
|
+
if (typeof mensaje === "object" &&
|
|
5166
|
+
mensaje !== null &&
|
|
5167
|
+
mensaje.clase === "eliminarEmulador" &&
|
|
5168
|
+
typeof mensaje.avd === "string") {
|
|
5169
|
+
void atenderEliminarEmulador(mensaje.avd).catch(contar);
|
|
5170
|
+
respuesta.writeHead(204);
|
|
5171
|
+
respuesta.end();
|
|
5172
|
+
return;
|
|
5173
|
+
}
|
|
5174
|
+
if (typeof mensaje === "object" &&
|
|
5175
|
+
mensaje !== null &&
|
|
5176
|
+
mensaje.clase === "crearEmulador" &&
|
|
5177
|
+
typeof mensaje.nombre === "string" &&
|
|
5178
|
+
(mensaje.base === undefined || typeof mensaje.base === "string") &&
|
|
5179
|
+
(mensaje.conDatos === undefined || typeof mensaje.conDatos === "boolean")) {
|
|
5180
|
+
// Suelto, como la receta: el progreso y la foto de después viajan por el SSE.
|
|
5181
|
+
void atenderCrearEmulador(mensaje.nombre, mensaje.base, mensaje.conDatos).catch(contar);
|
|
5182
|
+
respuesta.writeHead(204);
|
|
5183
|
+
respuesta.end();
|
|
5184
|
+
return;
|
|
5185
|
+
}
|
|
5186
|
+
if (typeof mensaje === "object" &&
|
|
5187
|
+
mensaje !== null &&
|
|
5188
|
+
mensaje.clase === "ajusteDeAvd" &&
|
|
5189
|
+
typeof mensaje.avd === "string" &&
|
|
5190
|
+
(mensaje.puerto === undefined || typeof mensaje.puerto === "number") &&
|
|
5191
|
+
(mensaje.sinVentana === undefined || typeof mensaje.sinVentana === "boolean")) {
|
|
5192
|
+
// Campo a campo: lo que no es un número o un booleano no llega a `settings.json`.
|
|
5193
|
+
void atenderAjusteDeAvd(mensaje.avd, {
|
|
5194
|
+
...(mensaje.puerto === undefined ? {} : { puerto: mensaje.puerto }),
|
|
5195
|
+
...(mensaje.sinVentana === undefined ? {} : { sinVentana: mensaje.sinVentana }),
|
|
5196
|
+
}).catch(contar);
|
|
5197
|
+
respuesta.writeHead(204);
|
|
5198
|
+
respuesta.end();
|
|
5199
|
+
return;
|
|
5200
|
+
}
|
|
5201
|
+
if (typeof mensaje === "object" &&
|
|
5202
|
+
mensaje !== null &&
|
|
5203
|
+
mensaje.clase === "pararEmulador" &&
|
|
5204
|
+
typeof mensaje.id === "string") {
|
|
5205
|
+
void atenderParadaDeEmulador(mensaje.id).catch(contar);
|
|
5206
|
+
respuesta.writeHead(204);
|
|
5207
|
+
respuesta.end();
|
|
5208
|
+
return;
|
|
5209
|
+
}
|
|
4891
5210
|
/**
|
|
4892
5211
|
* ANTES del `recibir` de la consola, y también antes que las demás clases de tarea: por
|
|
4893
5212
|
* aquí no entra nada hacia ningún turno. Ver `atenderMirar`.
|
|
@@ -4904,6 +5223,13 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
4904
5223
|
return;
|
|
4905
5224
|
}
|
|
4906
5225
|
if (typeof mensaje === "object" && mensaje !== null && mensaje.clase === "tarea") {
|
|
5226
|
+
if (mensaje.accion === "crear" && mudandoWorkspace) {
|
|
5227
|
+
// Una tarea creada a mitad de la mudanza guardaría la raíz VIEJA, que está a punto de
|
|
5228
|
+
// borrarse. Se niega con motivo, como cualquier otra negativa del cable.
|
|
5229
|
+
respuesta.writeHead(409, { "content-type": "application/json" });
|
|
5230
|
+
respuesta.end(JSON.stringify({ motivo: "se está cambiando la carpeta de los proyectos: crea la tarea cuando termine" }));
|
|
5231
|
+
return;
|
|
5232
|
+
}
|
|
4907
5233
|
if (mensaje.accion === "crear" && opciones.colaDeTareas !== undefined) {
|
|
4908
5234
|
const creada = atenderCrearTarea(mensaje.proyecto, mensaje.peticion, mensaje.encargo, mensaje.borrador);
|
|
4909
5235
|
if (creada !== undefined) {
|
|
@@ -4964,9 +5290,84 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
4964
5290
|
* Y se reemite SIEMPRE, se haya escrito o no: con el valor nuevo cuando valió y con el
|
|
4965
5291
|
* de antes cuando no, así el campo acaba enseñando lo que hay en disco y no lo que se
|
|
4966
5292
|
* tecleó. Un rechazo mudo dejaría la pantalla afirmando un ajuste que no está puesto.
|
|
5293
|
+
*
|
|
5294
|
+
* **Y cambiarla MUDA lo que ya está bajado**, en dos actos (`vestibulo.mudarWorkspace`):
|
|
5295
|
+
* `planear` contesta en la PROPIA respuesta qué se movería —por nombre y entorno, nunca
|
|
5296
|
+
* una ruta— o por qué no se puede, sin tocar nada; `aplicar` (o sin `accion`) contesta
|
|
5297
|
+
* 204 en el acto y el recorrido llega por `mudanzaDeWorkspace`, porque copiar y
|
|
5298
|
+
* verificar cientos de megas no cabe en una petición. La negativa que se sabe ANTES
|
|
5299
|
+
* de empezar —una ruta que no vale, una mudanza ya en marcha— va en un 409 con motivo.
|
|
4967
5300
|
*/
|
|
4968
|
-
|
|
4969
|
-
|
|
5301
|
+
const negarWorkspace = (motivo) => {
|
|
5302
|
+
respuesta.writeHead(409, { "content-type": "application/json" });
|
|
5303
|
+
respuesta.end(JSON.stringify({ motivo }));
|
|
5304
|
+
};
|
|
5305
|
+
const tareasVivas = () => (opciones.colaDeTareas?.listar() ?? []).map((t) => ({ estado: t.estado, raiz: t.proyecto.raiz }));
|
|
5306
|
+
const resuelta = opciones.resolverWorkspace?.(mensaje.ruta);
|
|
5307
|
+
if (resuelta === undefined || opciones.casaXonecode === undefined) {
|
|
5308
|
+
// Esta ejecución no sabe mudar: el comportamiento de siempre, solo escribir el ajuste.
|
|
5309
|
+
opciones.guardarWorkspace?.(mensaje.ruta);
|
|
5310
|
+
emitirWorkspace();
|
|
5311
|
+
respuesta.writeHead(204);
|
|
5312
|
+
respuesta.end();
|
|
5313
|
+
return;
|
|
5314
|
+
}
|
|
5315
|
+
if ("motivo" in resuelta) {
|
|
5316
|
+
emitirWorkspace();
|
|
5317
|
+
negarWorkspace(resuelta.motivo);
|
|
5318
|
+
return;
|
|
5319
|
+
}
|
|
5320
|
+
if (mensaje.accion === "planear") {
|
|
5321
|
+
const plan = vestibulo.planearWorkspace(resuelta.ruta, opciones.casaXonecode, tareasVivas());
|
|
5322
|
+
let megas;
|
|
5323
|
+
if (plan.motivo === undefined && plan.mudanzas.length > 0) {
|
|
5324
|
+
megas = await megasDeLasMudanzas(plan.mudanzas).catch(() => undefined);
|
|
5325
|
+
}
|
|
5326
|
+
respuesta.writeHead(200, { "content-type": "application/json" });
|
|
5327
|
+
respuesta.end(JSON.stringify({
|
|
5328
|
+
proyectos: plan.mudanzas.map((m) => ({ entorno: m.entorno, proyecto: m.proyecto })),
|
|
5329
|
+
...(megas === undefined ? {} : { megas }),
|
|
5330
|
+
...(plan.motivo === undefined ? {} : { motivo: plan.motivo }),
|
|
5331
|
+
}));
|
|
5332
|
+
return;
|
|
5333
|
+
}
|
|
5334
|
+
if (mudandoWorkspace) {
|
|
5335
|
+
negarWorkspace("ya hay un cambio de carpeta en marcha: espera a que termine");
|
|
5336
|
+
return;
|
|
5337
|
+
}
|
|
5338
|
+
mudandoWorkspace = true;
|
|
5339
|
+
const casaXonecode = opciones.casaXonecode;
|
|
5340
|
+
const guardar = opciones.guardarWorkspace ?? (() => { });
|
|
5341
|
+
void (async () => {
|
|
5342
|
+
try {
|
|
5343
|
+
const { resultado, cerroLaAbierta } = await vestibulo.mudarWorkspace({
|
|
5344
|
+
hacia: resuelta.ruta,
|
|
5345
|
+
casaXonecode,
|
|
5346
|
+
tareas: tareasVivas,
|
|
5347
|
+
guardar,
|
|
5348
|
+
progreso: (progreso) => {
|
|
5349
|
+
ultimoProgresoDeMudanza = progreso;
|
|
5350
|
+
emitir({ clase: "mudanzaDeWorkspace", progreso });
|
|
5351
|
+
},
|
|
5352
|
+
});
|
|
5353
|
+
// El cable se quedaría enganchado a una consola cerrada: al vestíbulo, como al
|
|
5354
|
+
// borrar una copia. El alta siguiente ya trae las copias en la base nueva.
|
|
5355
|
+
if (cerroLaAbierta)
|
|
5356
|
+
adjuntar();
|
|
5357
|
+
emitir({ clase: "mudanzaDeWorkspace", resultado });
|
|
5358
|
+
}
|
|
5359
|
+
catch (error) {
|
|
5360
|
+
contar(error);
|
|
5361
|
+
emitir({ clase: "mudanzaDeWorkspace", resultado: { estado: "rechazado", motivo: "no se pudo cambiar la carpeta" } });
|
|
5362
|
+
}
|
|
5363
|
+
finally {
|
|
5364
|
+
mudandoWorkspace = false;
|
|
5365
|
+
ultimoProgresoDeMudanza = undefined;
|
|
5366
|
+
emitirWorkspace();
|
|
5367
|
+
opciones.revisarTareas?.();
|
|
5368
|
+
await anunciarAlta().catch(contar);
|
|
5369
|
+
}
|
|
5370
|
+
})().catch(contar);
|
|
4970
5371
|
respuesta.writeHead(204);
|
|
4971
5372
|
respuesta.end();
|
|
4972
5373
|
return;
|
|
@@ -5173,6 +5574,10 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
5173
5574
|
// Suelto: `anunciarAlta` consulta CloudStudio y el `POST` no puede quedarse
|
|
5174
5575
|
// esperando por eso. El alta nueva llega por el SSE, como siempre.
|
|
5175
5576
|
void anunciarAlta().catch(contar);
|
|
5577
|
+
// Y la foto que hay, sin volver a medir: lo que cambió es QUIÉN usa ese aparato, y
|
|
5578
|
+
// Ajustes lo enseña al lado de cada uno (`enUso`).
|
|
5579
|
+
if (informeDeDispositivos !== undefined)
|
|
5580
|
+
emitir(mensajeDeDispositivos(informeDeDispositivos));
|
|
5176
5581
|
}
|
|
5177
5582
|
respuesta.writeHead(204);
|
|
5178
5583
|
respuesta.end();
|
|
@@ -5696,6 +6101,12 @@ export function ajusteDeWorkspaceCableado(opciones) {
|
|
|
5696
6101
|
const guardar = opciones.guardar ?? ((ruta) => void guardarWorkspaceEnDisco(undefined, ruta));
|
|
5697
6102
|
return {
|
|
5698
6103
|
workspace: () => leer() ?? baseDeWorkspacePorOmision(),
|
|
6104
|
+
casaXonecode: join(opciones.casa, ".xonecode"),
|
|
6105
|
+
resolverWorkspace: (ruta) => {
|
|
6106
|
+
const absoluta = expandirConCasa(ruta, opciones.casa);
|
|
6107
|
+
const motivo = motivoDeWorkspaceInaceptable(absoluta);
|
|
6108
|
+
return motivo === undefined ? { ruta: absoluta } : { motivo };
|
|
6109
|
+
},
|
|
5699
6110
|
guardarWorkspace: (ruta) => {
|
|
5700
6111
|
const absoluta = expandirConCasa(ruta, opciones.casa);
|
|
5701
6112
|
if (motivoDeWorkspaceInaceptable(absoluta) !== undefined)
|
|
@@ -5704,6 +6115,31 @@ export function ajusteDeWorkspaceCableado(opciones) {
|
|
|
5704
6115
|
},
|
|
5705
6116
|
};
|
|
5706
6117
|
}
|
|
6118
|
+
/**
|
|
6119
|
+
* Los siete puertos de los emuladores, cableados con las funciones de verdad — extraídos por el
|
|
6120
|
+
* MISMO motivo que `ajusteDeWorkspaceCableado`: dentro de `arrancarConsolaWeb` serían un literal
|
|
6121
|
+
* que ningún test mira, y cada uno es un campo OPCIONAL de `montarRutas`, así que olvidarlo o
|
|
6122
|
+
* perder un argumento por el camino compila y pasa.
|
|
6123
|
+
*
|
|
6124
|
+
* `casa` es la del `settings.json` donde se guardan los ajustes por AVD; ausente, la del usuario
|
|
6125
|
+
* (`guardarAjusteDeAvd(undefined, …)`). `arrancar` entra solo para que un test vea qué recibe la
|
|
6126
|
+
* de verdad sin lanzar un emulador.
|
|
6127
|
+
*/
|
|
6128
|
+
export function emuladoresCableados(opciones = {}) {
|
|
6129
|
+
const arrancar = opciones.arrancar ?? arrancarEmulador;
|
|
6130
|
+
return {
|
|
6131
|
+
arrancarEmulador: (avd, o) => arrancar(avd, {}, o ?? {}),
|
|
6132
|
+
// Con `conDatos` se CLONA la carpeta; sin él, solo la configuración de la base sobre un AVD vacío.
|
|
6133
|
+
crearAvd: (nombre, alSalirLinea, desde) => desde?.conDatos === true
|
|
6134
|
+
? clonarAvd(desde.base, nombre, { alSalirLinea })
|
|
6135
|
+
: crearAvd(nombre, { alSalirLinea }, desde === undefined ? {} : { base: desde.base }),
|
|
6136
|
+
borrarAvd: (nombre, alSalirLinea) => borrarAvd(nombre, { alSalirLinea }),
|
|
6137
|
+
pararEmulador: (serie) => pararEmulador(serie),
|
|
6138
|
+
guardarAjusteDeAvd: (avd, cambio) => void guardarAjusteDeAvd(opciones.casa, avd, cambio),
|
|
6139
|
+
quitarAjustesDeAvd: (avd) => void quitarAjustesDeAvd(opciones.casa, avd),
|
|
6140
|
+
guardarPuertosAsignados: (asignados) => void guardarPuertosAsignados(opciones.casa, asignados),
|
|
6141
|
+
};
|
|
6142
|
+
}
|
|
5707
6143
|
/**
|
|
5708
6144
|
* Las dos mitades de la casilla «Depurar», cableadas — extraídas por el MISMO motivo que
|
|
5709
6145
|
* `ajusteDeWorkspaceCableado`. `leer`/`guardar` entran por parámetro, y no como en el workspace
|
|
@@ -5756,8 +6192,28 @@ export function ajusteDeGestorCableado(ajuste) {
|
|
|
5756
6192
|
jira: crearGestorJira,
|
|
5757
6193
|
notion: crearGestorNotion,
|
|
5758
6194
|
};
|
|
6195
|
+
const sinServicio = () => {
|
|
6196
|
+
throw new Error("esta ejecución no tiene conectores");
|
|
6197
|
+
};
|
|
5759
6198
|
return {
|
|
5760
6199
|
conectores: (alCambiar) => (servicio = ajuste.conectores(alCambiar)),
|
|
6200
|
+
conectoresDeSesion: {
|
|
6201
|
+
delProyecto: (raiz) => {
|
|
6202
|
+
if (servicio === undefined)
|
|
6203
|
+
return [];
|
|
6204
|
+
const proyecto = cargar(raiz).config.proyecto;
|
|
6205
|
+
const lista = servicio.lista();
|
|
6206
|
+
return conectoresParaElAgente({
|
|
6207
|
+
marcados: proyecto?.conectores ?? [],
|
|
6208
|
+
gestor: proyecto?.gestorDeTareas?.conector,
|
|
6209
|
+
anadidos: lista.conectores.map((c) => c.id),
|
|
6210
|
+
catalogo: lista.catalogo,
|
|
6211
|
+
});
|
|
6212
|
+
},
|
|
6213
|
+
// `async`: sin servicio, el fallo sale como promesa RECHAZADA, que es lo que la tool devuelve como texto.
|
|
6214
|
+
tools: async (id) => (servicio === undefined ? sinServicio() : servicio.tools(id)),
|
|
6215
|
+
llamar: async (id, nombre, args, opciones) => (servicio === undefined ? sinServicio() : servicio.llamar(id, nombre, args, opciones)),
|
|
6216
|
+
},
|
|
5761
6217
|
gestorDeTareas: (conector) => {
|
|
5762
6218
|
const fabrica = Object.hasOwn(FABRICAS, conector) ? FABRICAS[conector] : undefined;
|
|
5763
6219
|
if (fabrica === undefined)
|
|
@@ -6005,7 +6461,10 @@ export async function arrancarConsolaWeb(opciones) {
|
|
|
6005
6461
|
escribir(`${texto}\n`);
|
|
6006
6462
|
vestibulo?.consola.consola.escribir(`${texto}\n`);
|
|
6007
6463
|
};
|
|
6008
|
-
|
|
6464
|
+
// Los conectores y el gestor de tareas, sobre UN servicio —construido luego por `montarRutas`—, y
|
|
6465
|
+
// las sesiones sobre ese mismo: por eso esto va ANTES del vestíbulo, que es quien las abre.
|
|
6466
|
+
const conectoresYGestor = ajusteDeGestorCableado(ajusteDeConectoresCableado({ casa: homedir() }));
|
|
6467
|
+
vestibulo = opciones.vestibulo ?? vestibuloReal(opciones, servidor, escribir, informar, conectoresYGestor.conectoresDeSesion);
|
|
6009
6468
|
const conVestibulo = vestibulo;
|
|
6010
6469
|
/**
|
|
6011
6470
|
* El corredor de tareas y su costura con el cable — `construirCorredorDeTareasCableado`
|
|
@@ -6098,7 +6557,8 @@ export async function arrancarConsolaWeb(opciones) {
|
|
|
6098
6557
|
// (`ajusteDeConectoresCableado`), nunca un literal aquí dentro.
|
|
6099
6558
|
// Y el gestor de tareas (IXCODE-11) llama por ESE mismo servicio: los dos salen de
|
|
6100
6559
|
// `ajusteDeGestorCableado`, que envuelve la fábrica de conectores.
|
|
6101
|
-
|
|
6560
|
+
conectores: conectoresYGestor.conectores,
|
|
6561
|
+
gestorDeTareas: conectoresYGestor.gestorDeTareas,
|
|
6102
6562
|
// El selector nativo, solo si este sistema tiene uno. En el que no, la opción no se
|
|
6103
6563
|
// monta y el botón no llega a existir.
|
|
6104
6564
|
...(haySelectorDeCarpeta()
|
|
@@ -6166,7 +6626,12 @@ export async function arrancarConsolaWeb(opciones) {
|
|
|
6166
6626
|
// nueve veces, y la que el comentario de justo abajo describe para las cuatro del
|
|
6167
6627
|
// lanzamiento. `montarRutas` lo trata como ausente si falta, así que olvidarlo aquí
|
|
6168
6628
|
// dejaría el botón diciendo «esta ejecución no puede arrancar emuladores» con todo verde.
|
|
6169
|
-
|
|
6629
|
+
// Los cinco de los emuladores —arrancar, crear, parar, y guardar lo de cada AVD—, con las
|
|
6630
|
+
// funciones REALES y compuestos fuera (`emuladoresCableados`) para que la composición tenga
|
|
6631
|
+
// test: `arrancarEmulador: (avd) => arrancarEmulador(avd)` compilaba igual perdiendo el
|
|
6632
|
+
// «sin ventana», y un campo opcional olvidado aquí deja el botón diciendo «esta ejecución no
|
|
6633
|
+
// puede» con todo en verde.
|
|
6634
|
+
...emuladoresCableados(),
|
|
6170
6635
|
/**
|
|
6171
6636
|
* Las cuatro de «¿se puede lanzar y lánzalo?», compuestas con las funciones REALES.
|
|
6172
6637
|
*
|
|
@@ -6327,10 +6792,10 @@ function esperarInterrupcion() {
|
|
|
6327
6792
|
}
|
|
6328
6793
|
/** El vestíbulo con todas sus piezas reales. Se construye DESPUÉS del servidor porque el
|
|
6329
6794
|
* callback de OAuth necesita saber a qué URL devolver al navegador. */
|
|
6330
|
-
function vestibuloReal(opciones, servidor, escribir, informar) {
|
|
6795
|
+
function vestibuloReal(opciones, servidor, escribir, informar, conectores) {
|
|
6331
6796
|
// Lo PRIMERO, antes de leer nada de disco: si esto va a fallar, que falle sin haber
|
|
6332
6797
|
// aplicado credenciales al proceso ni construido medio vestíbulo.
|
|
6333
|
-
const ejecutor = banderaDeEjecutor(opciones);
|
|
6798
|
+
const ejecutor = banderaDeEjecutor(opciones, conectores);
|
|
6334
6799
|
const cargado = cargar(opciones.cwd);
|
|
6335
6800
|
aplicarAuth(cargado.auth);
|
|
6336
6801
|
// La lectura NO se hace aquí: se hace al abrir cada consola, que es lo que hace que una
|
|
@@ -6441,12 +6906,18 @@ function vestibuloReal(opciones, servidor, escribir, informar) {
|
|
|
6441
6906
|
* O el ejecutor real, o el de pega PEDIDO con `--guion`, o un error. Lo que no hay es una
|
|
6442
6907
|
* tercera opción muda.
|
|
6443
6908
|
*/
|
|
6444
|
-
function banderaDeEjecutor(opciones) {
|
|
6909
|
+
export function banderaDeEjecutor(opciones, conectores) {
|
|
6445
6910
|
if (opciones.guion === true)
|
|
6446
6911
|
return {};
|
|
6447
|
-
|
|
6912
|
+
const crear = opciones.crearEjecutor;
|
|
6913
|
+
if (crear === undefined) {
|
|
6448
6914
|
throw new Error("esta consola web se montó sin `crearEjecutor`: correría el agente de pega sin decirlo (usa --guion si es lo que quieres)");
|
|
6449
6915
|
}
|
|
6450
|
-
|
|
6916
|
+
// Los conectores se AÑADEN aquí, a todas las consolas —de persona y de tarea—: el vestíbulo no
|
|
6917
|
+
// tiene que acordarse de pasarlos en cada puerta. En una tarea, lo que escriben se rechaza igual
|
|
6918
|
+
// (`consolaDeTarea.ts`).
|
|
6919
|
+
if (conectores === undefined)
|
|
6920
|
+
return { crearEjecutor: crear };
|
|
6921
|
+
return { crearEjecutor: (alAbrir, o) => crear(alAbrir, { ...o, conectores }) };
|
|
6451
6922
|
}
|
|
6452
6923
|
//# sourceMappingURL=arranque.js.map
|