xonecode 0.21.0 → 0.22.1
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-UkjSyds-.js → php-BTFUgUDo.js} +1 -1
- package/apps/web/dist/assets/{ruby-e801JuzW.js → ruby-BKKiBtJK.js} +1 -1
- package/apps/web/dist/index.html +2 -2
- package/dist/agent/cloudstudio/cloudstudioClient.js +32 -5
- package/dist/agent/cloudstudio/cloudstudioClient.js.map +1 -1
- package/dist/agent/cloudstudio/descarga.js +10 -0
- package/dist/agent/cloudstudio/descarga.js.map +1 -1
- package/dist/agent/cloudstudio/subida.js +31 -6
- package/dist/agent/cloudstudio/subida.js.map +1 -1
- package/dist/agent/cloudstudio/validarTrasDescarga.js +79 -0
- package/dist/agent/cloudstudio/validarTrasDescarga.js.map +1 -0
- 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/cli/main.js +5 -0
- package/dist/cli/main.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/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/validacionDeEscritura.js +19 -11
- package/dist/core/validacionDeEscritura.js.map +1 -1
- package/dist/web/servidor/arranque.js +476 -43
- package/dist/web/servidor/arranque.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-CFoqpKRk.css +0 -1
- package/apps/web/dist/assets/index-DgvLb2SM.js +0 -486
|
@@ -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";
|
|
@@ -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)
|
|
@@ -1130,7 +1141,7 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
1130
1141
|
// La foto de la máquina, si ya se tomó. Si no, se dispara abajo UNA vez y llega a
|
|
1131
1142
|
// todos por el SSE cuando termine: no se espera aquí, que son varios procesos.
|
|
1132
1143
|
if (informeDeDispositivos !== undefined) {
|
|
1133
|
-
cliente(
|
|
1144
|
+
cliente(mensajeDeDispositivos(informeDeDispositivos));
|
|
1134
1145
|
}
|
|
1135
1146
|
// Y si hay turno corriendo, se dice: quien conecta a mitad no vio el mensaje que lo
|
|
1136
1147
|
// anunció, y sin esto vería el compositor encendido y sin borde —«no pasa nada»—
|
|
@@ -1275,6 +1286,8 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
1275
1286
|
? { clase: "workspace", ruta }
|
|
1276
1287
|
: { clase: "workspace", ruta, puedeElegir: true };
|
|
1277
1288
|
};
|
|
1289
|
+
/** Hay una mudanza del workspace en marcha: una segunda se niega, y crear una tarea también. */
|
|
1290
|
+
let mudandoWorkspace = false;
|
|
1278
1291
|
const emitirWorkspace = () => {
|
|
1279
1292
|
const m = mensajeDeWorkspace();
|
|
1280
1293
|
if (m !== undefined)
|
|
@@ -1663,6 +1676,30 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
1663
1676
|
};
|
|
1664
1677
|
/** Los ajustes de destino, o `{}` —que es «se miran todos»— si esta ejecución no los lee. */
|
|
1665
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
|
+
});
|
|
1666
1703
|
let deteccionEnVuelo;
|
|
1667
1704
|
/**
|
|
1668
1705
|
* Cómo acabó el último «Arrancar». Viaja con la foto porque el resultado de arrancar ES la
|
|
@@ -1679,10 +1716,24 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
1679
1716
|
deteccionEnVuelo = (async () => {
|
|
1680
1717
|
try {
|
|
1681
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
|
+
}
|
|
1682
1735
|
emitir({
|
|
1683
|
-
|
|
1684
|
-
informe: informeDeDispositivos,
|
|
1685
|
-
ajustes: ajustesDeDispositivos(),
|
|
1736
|
+
...mensajeDeDispositivos(informeDeDispositivos),
|
|
1686
1737
|
...(ultimoArranque === undefined ? {} : { arranque: ultimoArranque }),
|
|
1687
1738
|
});
|
|
1688
1739
|
}
|
|
@@ -1733,7 +1784,9 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
1733
1784
|
}
|
|
1734
1785
|
arranqueEnVuelo = avd;
|
|
1735
1786
|
try {
|
|
1736
|
-
|
|
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 });
|
|
1737
1790
|
ultimoArranque = { avd, ...r };
|
|
1738
1791
|
}
|
|
1739
1792
|
catch (error) {
|
|
@@ -1798,7 +1851,80 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
1798
1851
|
...informeDeDispositivos,
|
|
1799
1852
|
dispositivos: informeDeDispositivos.dispositivos.map((d) => (d.id === id ? { ...d, verificado } : d)),
|
|
1800
1853
|
};
|
|
1801
|
-
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
|
+
}
|
|
1802
1928
|
};
|
|
1803
1929
|
const atenderCatalogo = async (proveedor) => {
|
|
1804
1930
|
if (!proveedorConocido(proveedor))
|
|
@@ -2927,7 +3053,17 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
2927
3053
|
// doble mal hecho, o un bug suyo en el camino síncrono— dejaría el recorrido en
|
|
2928
3054
|
// `corriendo` para siempre y el botón muerto, que es justo lo que este `try` existe para
|
|
2929
3055
|
// evitar. Su contrato es no lanzar nunca; esto es por si deja de cumplirlo.
|
|
2930
|
-
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 });
|
|
2931
3067
|
trabajo.cancelar = enCurso.cancelar;
|
|
2932
3068
|
const resultado = await enCurso.terminado;
|
|
2933
3069
|
// El último SIEMPRE se emite, aunque no haya pasado el plazo: es el que dice cómo acabó.
|
|
@@ -3675,40 +3811,25 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
3675
3811
|
ultimoProgreso = Date.now();
|
|
3676
3812
|
};
|
|
3677
3813
|
/**
|
|
3678
|
-
*
|
|
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.
|
|
3679
3818
|
*
|
|
3680
|
-
* 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
|
|
3681
3820
|
* instalador es lo que él cree, y la foto es lo que hay. Es la misma regla que ya sigue
|
|
3682
3821
|
* `instalar`.
|
|
3683
3822
|
*/
|
|
3684
|
-
const
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
emitirProgreso("corriendo");
|
|
3692
|
-
return;
|
|
3693
|
-
}
|
|
3694
|
-
const correr = opciones.correrPasoDeReceta;
|
|
3695
|
-
if (correr === undefined) {
|
|
3696
|
-
informar("esta ejecución no puede ejecutar pasos de instalación");
|
|
3697
|
-
return;
|
|
3698
|
-
}
|
|
3699
|
-
const enMarcha = correr(mensaje.id, mensaje.paso, (linea) => {
|
|
3700
|
-
if (trabajo === undefined)
|
|
3701
|
-
return;
|
|
3702
|
-
trabajo.lineas.push(linea);
|
|
3703
|
-
// A ritmo: cada emisión manda la cola entera, y por línea sería cuadrático en bytes
|
|
3704
|
-
// con un `sdkmanager` que habla cada pocos milisegundos. Es la misma razón que
|
|
3705
|
-
// `MS_ENTRE_PARCIALES` en la piel web.
|
|
3706
|
-
if (Date.now() - ultimoProgreso >= MS_ENTRE_PROGRESOS)
|
|
3707
|
-
emitirProgreso("corriendo");
|
|
3708
|
-
});
|
|
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) => {
|
|
3709
3830
|
trabajo = {
|
|
3710
|
-
receta
|
|
3711
|
-
paso
|
|
3831
|
+
receta,
|
|
3832
|
+
paso,
|
|
3712
3833
|
titulo: enMarcha.titulo,
|
|
3713
3834
|
lineas: [],
|
|
3714
3835
|
cancelar: enMarcha.cancelar,
|
|
@@ -3717,6 +3838,12 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
3717
3838
|
emitirProgreso("corriendo");
|
|
3718
3839
|
void (async () => {
|
|
3719
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
|
+
}
|
|
3720
3847
|
// El último progreso SIEMPRE se emite, aunque no haya pasado el plazo: es el que
|
|
3721
3848
|
// completa el log y el que dice cómo acabó.
|
|
3722
3849
|
ultimoProgreso = 0;
|
|
@@ -3727,6 +3854,145 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
3727
3854
|
await atenderDispositivos().catch(contar);
|
|
3728
3855
|
})();
|
|
3729
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
|
+
};
|
|
3730
3996
|
/**
|
|
3731
3997
|
* Los modelos de un motor externo, para el desplegable de un subagente.
|
|
3732
3998
|
*
|
|
@@ -4896,6 +5162,51 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
4896
5162
|
respuesta.end();
|
|
4897
5163
|
return;
|
|
4898
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
|
+
}
|
|
4899
5210
|
/**
|
|
4900
5211
|
* ANTES del `recibir` de la consola, y también antes que las demás clases de tarea: por
|
|
4901
5212
|
* aquí no entra nada hacia ningún turno. Ver `atenderMirar`.
|
|
@@ -4912,6 +5223,13 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
4912
5223
|
return;
|
|
4913
5224
|
}
|
|
4914
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
|
+
}
|
|
4915
5233
|
if (mensaje.accion === "crear" && opciones.colaDeTareas !== undefined) {
|
|
4916
5234
|
const creada = atenderCrearTarea(mensaje.proyecto, mensaje.peticion, mensaje.encargo, mensaje.borrador);
|
|
4917
5235
|
if (creada !== undefined) {
|
|
@@ -4972,9 +5290,84 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
4972
5290
|
* Y se reemite SIEMPRE, se haya escrito o no: con el valor nuevo cuando valió y con el
|
|
4973
5291
|
* de antes cuando no, así el campo acaba enseñando lo que hay en disco y no lo que se
|
|
4974
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.
|
|
4975
5300
|
*/
|
|
4976
|
-
|
|
4977
|
-
|
|
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);
|
|
4978
5371
|
respuesta.writeHead(204);
|
|
4979
5372
|
respuesta.end();
|
|
4980
5373
|
return;
|
|
@@ -5181,6 +5574,10 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
5181
5574
|
// Suelto: `anunciarAlta` consulta CloudStudio y el `POST` no puede quedarse
|
|
5182
5575
|
// esperando por eso. El alta nueva llega por el SSE, como siempre.
|
|
5183
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));
|
|
5184
5581
|
}
|
|
5185
5582
|
respuesta.writeHead(204);
|
|
5186
5583
|
respuesta.end();
|
|
@@ -5704,6 +6101,12 @@ export function ajusteDeWorkspaceCableado(opciones) {
|
|
|
5704
6101
|
const guardar = opciones.guardar ?? ((ruta) => void guardarWorkspaceEnDisco(undefined, ruta));
|
|
5705
6102
|
return {
|
|
5706
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
|
+
},
|
|
5707
6110
|
guardarWorkspace: (ruta) => {
|
|
5708
6111
|
const absoluta = expandirConCasa(ruta, opciones.casa);
|
|
5709
6112
|
if (motivoDeWorkspaceInaceptable(absoluta) !== undefined)
|
|
@@ -5712,6 +6115,31 @@ export function ajusteDeWorkspaceCableado(opciones) {
|
|
|
5712
6115
|
},
|
|
5713
6116
|
};
|
|
5714
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
|
+
}
|
|
5715
6143
|
/**
|
|
5716
6144
|
* Las dos mitades de la casilla «Depurar», cableadas — extraídas por el MISMO motivo que
|
|
5717
6145
|
* `ajusteDeWorkspaceCableado`. `leer`/`guardar` entran por parámetro, y no como en el workspace
|
|
@@ -6198,7 +6626,12 @@ export async function arrancarConsolaWeb(opciones) {
|
|
|
6198
6626
|
// nueve veces, y la que el comentario de justo abajo describe para las cuatro del
|
|
6199
6627
|
// lanzamiento. `montarRutas` lo trata como ausente si falta, así que olvidarlo aquí
|
|
6200
6628
|
// dejaría el botón diciendo «esta ejecución no puede arrancar emuladores» con todo verde.
|
|
6201
|
-
|
|
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(),
|
|
6202
6635
|
/**
|
|
6203
6636
|
* Las cuatro de «¿se puede lanzar y lánzalo?», compuestas con las funciones REALES.
|
|
6204
6637
|
*
|