xonecode 0.26.0 → 0.28.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/{EditorDeFichero-BOMIz5Wx.js → EditorDeFichero-ChyJtiiz.js} +1 -1
- package/apps/web/dist/assets/{index-DTsPoUh_.js → index-CFIrUOmW.js} +84 -84
- package/apps/web/dist/assets/{index-DICdq4UC.css → index-IptObD2z.css} +1 -1
- package/apps/web/dist/assets/{php-BHWCwDKR.js → php-B91ZcS-9.js} +1 -1
- package/apps/web/dist/assets/{ruby-D3HLU4IP.js → ruby-DrxrTSz0.js} +1 -1
- package/apps/web/dist/index.html +2 -2
- package/dist/agent/cloudstudio/subida.js +99 -26
- package/dist/agent/cloudstudio/subida.js.map +1 -1
- package/dist/agent/dispositivos/hotswap.js +27 -0
- package/dist/agent/dispositivos/hotswap.js.map +1 -1
- package/dist/agent/dispositivos/lanzamientoEnMaquina.js +33 -1
- package/dist/agent/dispositivos/lanzamientoEnMaquina.js.map +1 -1
- package/dist/agent/dispositivos/recargaEnAparato.js +149 -0
- package/dist/agent/dispositivos/recargaEnAparato.js.map +1 -0
- package/dist/agent/grafo/arbolDeProyecto.js +34 -11
- package/dist/agent/grafo/arbolDeProyecto.js.map +1 -1
- package/dist/agent/grafo/codificacionDelProyecto.js +282 -0
- package/dist/agent/grafo/codificacionDelProyecto.js.map +1 -0
- package/dist/agent/grafo/proyecto.js +6 -1
- package/dist/agent/grafo/proyecto.js.map +1 -1
- package/dist/agent/motores/trueforge/sesionTrueforge.js +4 -1
- package/dist/agent/motores/trueforge/sesionTrueforge.js.map +1 -1
- package/dist/agent/sesiones/baseDeFichero.js +8 -8
- package/dist/agent/sesiones/baseDeFichero.js.map +1 -1
- package/dist/agent/subagentes/escrituraExterna.js +4 -1
- package/dist/agent/subagentes/escrituraExterna.js.map +1 -1
- package/dist/agent/turno/turnoReal.js +4 -1
- package/dist/agent/turno/turnoReal.js.map +1 -1
- package/dist/cli/main.js +7 -1
- package/dist/cli/main.js.map +1 -1
- package/dist/core/actos.js.map +1 -1
- package/dist/core/codificacion.js +134 -0
- package/dist/core/codificacion.js.map +1 -0
- package/dist/core/ports.js +22 -2
- package/dist/core/ports.js.map +1 -1
- package/dist/core/recargaEnCaliente.js +114 -0
- package/dist/core/recargaEnCaliente.js.map +1 -0
- package/dist/core/verificacionDeSubida.js +58 -0
- package/dist/core/verificacionDeSubida.js.map +1 -0
- package/dist/web/servidor/arranque.js +184 -5
- package/dist/web/servidor/arranque.js.map +1 -1
- package/dist/web/servidor/transporte.js.map +1 -1
- package/package.json +1 -1
- package/skills/xone-hotswap/scripts/xone-desplegar-android +62 -24
- package/skills/xone-hotswap/scripts/xone-recargar-android +23 -1
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* La comprobación de lo que una subida dejó en CloudStudio: lo que se mandó contra lo que se
|
|
3
|
+
* relee. PURA, para poder probar el motivo sin red.
|
|
4
|
+
*
|
|
5
|
+
* Existe porque hay ficheros que se CORTAN al subirlos a Studio y la causa no se ha encontrado.
|
|
6
|
+
* Mientras tanto, cada texto subido se relee y se compara; un corte tiene que salir como fallo
|
|
7
|
+
* con cifras que se puedan medir —cuánto había, cuánto llegó, desde dónde difiere—, no como un
|
|
8
|
+
* «subido» que miente.
|
|
9
|
+
*
|
|
10
|
+
* La comparación normaliza SOLO dos transformaciones MEDIDAS contra el servidor real (una rama
|
|
11
|
+
* desechable de AppDemo), aplicadas a los dos lados:
|
|
12
|
+
* - **Studio quita el salto de línea FINAL** (o `studio_get_file` no lo devuelve): un
|
|
13
|
+
* «…'añadir';\n» de 76 B vuelve con 75 B, y un fichero de 457.890 B vuelve con 457.889 B,
|
|
14
|
+
* idéntico en todo lo demás. Un fichero SIN salto final vuelve idéntico.
|
|
15
|
+
* - **Studio convierte CRLF en LF** («\r\n» → «\n»).
|
|
16
|
+
* Nada más: el BOM se conserva, y tildes, emoji y «» llegan intactos, así que no se tocan.
|
|
17
|
+
* Normalizar a ciegas es justo cómo se esconde un corte; un corte de verdad (falta una línea,
|
|
18
|
+
* cambia algo en medio) sigue saliendo distinto. El motivo y los hashes se calculan sobre los
|
|
19
|
+
* textos EN CRUDO: son lo que hay de verdad a cada lado.
|
|
20
|
+
*/
|
|
21
|
+
import { createHash } from "node:crypto";
|
|
22
|
+
/** sha256 en hexadecimal minúsculo: el mismo formato que pide `studio_upload_file`. */
|
|
23
|
+
export function sha256Hex(datos) {
|
|
24
|
+
return createHash("sha256").update(datos).digest("hex");
|
|
25
|
+
}
|
|
26
|
+
/** Un número con el punto de miles del castellano, sin depender del ICU de la máquina. */
|
|
27
|
+
export function conMiles(n) {
|
|
28
|
+
return String(n).replace(/\B(?=(\d{3})+(?!\d))/g, ".");
|
|
29
|
+
}
|
|
30
|
+
/** Las dos transformaciones medidas de Studio (ver cabecera), y ninguna más. */
|
|
31
|
+
function comoLoGuardaStudio(texto) {
|
|
32
|
+
return texto.replace(/\r\n/g, "\n").replace(/\n+$/, "");
|
|
33
|
+
}
|
|
34
|
+
/** `undefined` = llegó igual. Si no, el motivo con las cifras y los dos hashes. */
|
|
35
|
+
export function diferenciaDeTexto(local, studio) {
|
|
36
|
+
if (comoLoGuardaStudio(local) === comoLoGuardaStudio(studio))
|
|
37
|
+
return undefined;
|
|
38
|
+
let desde = 0;
|
|
39
|
+
const tope = Math.min(local.length, studio.length);
|
|
40
|
+
while (desde < tope && local[desde] === studio[desde])
|
|
41
|
+
desde++;
|
|
42
|
+
const bytesLocal = Buffer.byteLength(local, "utf8");
|
|
43
|
+
const bytesStudio = Buffer.byteLength(studio, "utf8");
|
|
44
|
+
return {
|
|
45
|
+
motivo: `llegó distinto a Studio: ${conMiles(bytesLocal)} B en local, ${conMiles(bytesStudio)} B en Studio ` +
|
|
46
|
+
`(difieren desde el carácter ${conMiles(desde)})`,
|
|
47
|
+
sha256Local: sha256Hex(local),
|
|
48
|
+
sha256Studio: sha256Hex(studio),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/** Cuánto de un motivo de error remoto se deja en un fallo: lo justo para reconocerlo. */
|
|
52
|
+
const TOPE_DE_MOTIVO = 200;
|
|
53
|
+
/** El motivo de una relectura que no se pudo hacer: el fichero se escribió pero NO está comprobado. */
|
|
54
|
+
export function motivoSinComprobar(error) {
|
|
55
|
+
const corto = error.length > TOPE_DE_MOTIVO ? `${error.slice(0, TOPE_DE_MOTIVO)}…` : error;
|
|
56
|
+
return `subido pero no se pudo comprobar: ${corto}`;
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=verificacionDeSubida.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verificacionDeSubida.js","sourceRoot":"","sources":["../../src/core/verificacionDeSubida.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,uFAAuF;AACvF,MAAM,UAAU,SAAS,CAAC,KAA0B;IAClD,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC1D,CAAC;AAED,0FAA0F;AAC1F,MAAM,UAAU,QAAQ,CAAC,CAAS;IAChC,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,uBAAuB,EAAE,GAAG,CAAC,CAAC;AACzD,CAAC;AASD,gFAAgF;AAChF,SAAS,kBAAkB,CAAC,KAAa;IACvC,OAAO,KAAK,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AAC1D,CAAC;AAED,mFAAmF;AACnF,MAAM,UAAU,iBAAiB,CAAC,KAAa,EAAE,MAAc;IAC7D,IAAI,kBAAkB,CAAC,KAAK,CAAC,KAAK,kBAAkB,CAAC,MAAM,CAAC;QAAE,OAAO,SAAS,CAAC;IAC/E,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;IACnD,OAAO,KAAK,GAAG,IAAI,IAAI,KAAK,CAAC,KAAK,CAAC,KAAK,MAAM,CAAC,KAAK,CAAC;QAAE,KAAK,EAAE,CAAC;IAC/D,MAAM,UAAU,GAAG,MAAM,CAAC,UAAU,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IACpD,MAAM,WAAW,GAAG,MAAM,CAAC,UAAU,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtD,OAAO;QACL,MAAM,EACJ,4BAA4B,QAAQ,CAAC,UAAU,CAAC,gBAAgB,QAAQ,CAAC,WAAW,CAAC,eAAe;YACpG,+BAA+B,QAAQ,CAAC,KAAK,CAAC,GAAG;QACnD,WAAW,EAAE,SAAS,CAAC,KAAK,CAAC;QAC7B,YAAY,EAAE,SAAS,CAAC,MAAM,CAAC;KAChC,CAAC;AACJ,CAAC;AAED,0FAA0F;AAC1F,MAAM,cAAc,GAAG,GAAG,CAAC;AAE3B,uGAAuG;AACvG,MAAM,UAAU,kBAAkB,CAAC,KAAa;IAC9C,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,GAAG,cAAc,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC;IAC3F,OAAO,qCAAqC,KAAK,EAAE,CAAC;AACtD,CAAC"}
|
|
@@ -55,8 +55,11 @@ import { PLATAFORMAS_DE_DISPOSITIVO } from "../../core/settings.js";
|
|
|
55
55
|
import { instalarHerramientaDeDispositivos, verificarDispositivo } from "../../agent/dispositivos/dispositivosEnMaquina.js";
|
|
56
56
|
import { arrancarEmulador, pararEmulador } from "../../agent/dispositivos/arranqueDeEmulador.js";
|
|
57
57
|
import { borrarAvd, clonarAvd, correrPasoDeReceta, crearAvd } from "../../agent/dispositivos/instalacionEnMaquina.js";
|
|
58
|
-
import { asignarPuertosPendientes, motivoDeNombreDeAvdInaceptable, motivoDePuertoInaceptable, puertoDeAvd, } from "../../core/puertosDeAvd.js";
|
|
58
|
+
import { asignarPuertosPendientes, motivoDeNombreDeAvdInaceptable, motivoDePuertoInaceptable, puertoDeAvd, PUERTO_DEL_HOTSWAP, } from "../../core/puertosDeAvd.js";
|
|
59
59
|
import { abrirCarpetaDelSistema, abrirDirectorioDelSistema } from "../../agent/config/selectorEnMaquina.js";
|
|
60
|
+
import { recargarFicheroDelProyecto, relanzarEnAparato, } from "../../agent/dispositivos/recargaEnAparato.js";
|
|
61
|
+
import { textoDeRecarga } from "../../core/recargaEnCaliente.js";
|
|
62
|
+
import { nombreDeApp } from "../../core/descriptoresDeApp.js";
|
|
60
63
|
import { modelosDeMotor } from "../../agent/config/modelosDeMotor.js";
|
|
61
64
|
import { parsear, compatibleConOpenAi, esProveedorPersonalizado, idDeProveedorPersonalizado, motivoDeEndpointInaceptable, motivoDeSlugInaceptable, nombreDeProveedor, PROVEEDORES, slugDesdeNombre, resolver, SIN_CREDENCIAL, PAPELES, } from "../../core/modelos.js";
|
|
62
65
|
import { hayCredencial } from "../../cli/consola.js";
|
|
@@ -3020,15 +3023,21 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
3020
3023
|
* se pintó el botón. Una revalidación que no pasa se contesta con un `lanzamiento` en `fallo`
|
|
3021
3024
|
* y NO se llama al lanzador.
|
|
3022
3025
|
*/
|
|
3023
|
-
|
|
3026
|
+
/**
|
|
3027
|
+
* `alAcabar` es para quien lanzó por OTRO camino que la pestaña (el «Probar» del editor): le dice
|
|
3028
|
+
* cómo acabó, para que su línea no se quede en «desplegando» para siempre.
|
|
3029
|
+
*/
|
|
3030
|
+
const atenderLanzarApp = async (alAcabar) => {
|
|
3024
3031
|
// Ya hay uno: se reenvía su estado en vez de lanzar otro, como `atenderReceta`.
|
|
3025
3032
|
if (lanzamiento !== undefined) {
|
|
3026
3033
|
emitirLanzamiento("corriendo");
|
|
3034
|
+
alAcabar?.("ocupado");
|
|
3027
3035
|
return;
|
|
3028
3036
|
}
|
|
3029
3037
|
const medida = await medirLanzamiento();
|
|
3030
3038
|
if (medida === undefined) {
|
|
3031
3039
|
informar("no hay ningún proyecto abierto que lanzar");
|
|
3040
|
+
alAcabar?.("fallo", "no hay ningún proyecto abierto");
|
|
3032
3041
|
return;
|
|
3033
3042
|
}
|
|
3034
3043
|
const foto = medida.dispositivo === undefined ? undefined : fotoDeDispositivo(medida.dispositivo);
|
|
@@ -3043,6 +3052,7 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
3043
3052
|
ms: 0,
|
|
3044
3053
|
motivo,
|
|
3045
3054
|
});
|
|
3055
|
+
alAcabar?.("fallo", motivo);
|
|
3046
3056
|
};
|
|
3047
3057
|
const lanzar = opciones.lanzarEnDispositivo;
|
|
3048
3058
|
const primeraFalta = medida.veredicto.causas[0];
|
|
@@ -3127,6 +3137,7 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
3127
3137
|
ultimoLanzamiento = 0;
|
|
3128
3138
|
trabajo.fase = resultado.fase;
|
|
3129
3139
|
emitirLanzamiento(resultado.estado, resultado.motivo);
|
|
3140
|
+
alAcabar?.(resultado.estado === "ok" ? "ok" : "fallo", resultado.motivo ?? (resultado.estado === "ok" ? undefined : resultado.estado));
|
|
3130
3141
|
}
|
|
3131
3142
|
catch (error) {
|
|
3132
3143
|
// El contrato del lanzador es no lanzar nunca, así que esto es un bug suyo; aun así hay
|
|
@@ -3134,6 +3145,7 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
3134
3145
|
// muerto y sin una palabra.
|
|
3135
3146
|
ultimoLanzamiento = 0;
|
|
3136
3147
|
emitirLanzamiento("fallo", motivoLegible(error));
|
|
3148
|
+
alAcabar?.("fallo", motivoLegible(error));
|
|
3137
3149
|
}
|
|
3138
3150
|
finally {
|
|
3139
3151
|
lanzamiento = undefined;
|
|
@@ -3814,7 +3826,7 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
3814
3826
|
* - lo demás —guardas de ruta, huella, tope— lo decide el puerto y lo DEVUELVE.
|
|
3815
3827
|
* El `try` es el de `atenderFichero`: un fallo inesperado contesta sin la ruta de la máquina.
|
|
3816
3828
|
*/
|
|
3817
|
-
const atenderGuardarFichero = async (ruta, texto, huella, id, proyecto) => {
|
|
3829
|
+
const atenderGuardarFichero = async (ruta, texto, huella, id, proyecto, recargar = false) => {
|
|
3818
3830
|
const abierto = vestibulo.proyectoAbierto();
|
|
3819
3831
|
if (abierto === undefined) {
|
|
3820
3832
|
emitir({ clase: "ficheroGuardado", ruta, id, error: "no hay ningún proyecto abierto" });
|
|
@@ -3842,13 +3854,152 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
3842
3854
|
// `id` lo pone la pestaña en CADA guardado y vuelve en la respuesta: esta va a todas las
|
|
3843
3855
|
// pestañas, y cada una reconoce la suya por él. La huella no vale —dos pestañas que parten de
|
|
3844
3856
|
// la misma versión mandan la misma—.
|
|
3845
|
-
|
|
3857
|
+
const guardado = await opciones.escribirFichero(abierto.raiz, ruta, texto, huella);
|
|
3858
|
+
emitir({ clase: "ficheroGuardado", ...guardado, id });
|
|
3859
|
+
// DESPUÉS de contestar el guardado, y sin esperarla: el «Guardando…» no depende del aparato,
|
|
3860
|
+
// y nada de la recarga puede convertir un guardado bueno en uno fallido.
|
|
3861
|
+
if (recargar && guardado.huella !== undefined && guardado.error === undefined) {
|
|
3862
|
+
void atenderRecarga(abierto.raiz, ruta, { probar: false }).catch(contar);
|
|
3863
|
+
}
|
|
3846
3864
|
}
|
|
3847
3865
|
catch (error) {
|
|
3848
3866
|
informar(`no se pudo guardar «${ruta}» (${codigoDe(error)})`);
|
|
3849
3867
|
emitir({ clase: "ficheroGuardado", ruta, id, error: "no se pudo guardar el fichero" });
|
|
3850
3868
|
}
|
|
3851
3869
|
};
|
|
3870
|
+
/**
|
|
3871
|
+
* A qué aparato y app va una recarga, resuelto aquí y no en el cliente: el dispositivo ELEGIDO en
|
|
3872
|
+
* la sesión (si no hay, no se recarga — sin adivinar uno), Android, el puerto de SU AVD (con dos
|
|
3873
|
+
* emuladores, el 8443 le hablaría al otro sin error) y el nombre de la app de `app.ini`, el
|
|
3874
|
+
* MISMO que usa Ejecutar. `undefined` = no hay a dónde recargar, y eso no es un fallo.
|
|
3875
|
+
*/
|
|
3876
|
+
const destinoDeRecarga = async (abierto) => {
|
|
3877
|
+
if (abierto.dispositivo === undefined)
|
|
3878
|
+
return { motivo: "no hay un aparato elegido en la sesión" };
|
|
3879
|
+
const { dispositivo } = dispositivoDeLaSesion(abierto.dispositivo, informeDeDispositivos);
|
|
3880
|
+
// La foto de Dispositivos solo se toma al conectar y al pulsar «Refrescar»: un emulador
|
|
3881
|
+
// arrancado después no está en ella, y decir «la app no corre» mandaría a buscar donde no es.
|
|
3882
|
+
if (dispositivo === undefined) {
|
|
3883
|
+
return { motivo: "el aparato de la sesión no está en la última medida: pulsa Refrescar en Dispositivos" };
|
|
3884
|
+
}
|
|
3885
|
+
if (dispositivo.plataforma !== "android")
|
|
3886
|
+
return { motivo: "la recarga en caliente solo existe en Android" };
|
|
3887
|
+
const ini = await leerDescriptor(abierto.raiz, "app.ini");
|
|
3888
|
+
const app = ini.texto === undefined ? undefined : nombreDeApp(ini.texto);
|
|
3889
|
+
if (app === undefined)
|
|
3890
|
+
return { motivo: "app.ini no dice el nombre de la app (name=)" };
|
|
3891
|
+
return {
|
|
3892
|
+
serie: dispositivo.id,
|
|
3893
|
+
puerto: dispositivo.avd === undefined ? PUERTO_DEL_HOTSWAP : puertoDeAvd(ajustesDeDispositivos(), dispositivo.avd),
|
|
3894
|
+
app,
|
|
3895
|
+
};
|
|
3896
|
+
};
|
|
3897
|
+
/**
|
|
3898
|
+
* UNA recarga a la vez para toda la máquina, como el lanzamiento: dos al mismo aparato se
|
|
3899
|
+
* cruzarían en el canal (las respuestas se emparejan por ORDEN). Un guardado que llega con una en
|
|
3900
|
+
* vuelo deja su ruta como SIGUIENTE, y la última pisa a la anterior: tras varios Cmd+S seguidos lo
|
|
3901
|
+
* que importa es cómo quedó el fichero, no cada versión intermedia.
|
|
3902
|
+
*/
|
|
3903
|
+
let recargando = false;
|
|
3904
|
+
let siguienteRecarga;
|
|
3905
|
+
const emitirRecarga = (ruta, resultado) => {
|
|
3906
|
+
emitir({
|
|
3907
|
+
clase: "recargaEnAparato",
|
|
3908
|
+
ruta,
|
|
3909
|
+
estado: resultado.estado,
|
|
3910
|
+
...(resultado.clase === undefined ? {} : { tipo: resultado.clase }),
|
|
3911
|
+
texto: textoDeRecarga(resultado),
|
|
3912
|
+
});
|
|
3913
|
+
};
|
|
3914
|
+
/**
|
|
3915
|
+
* Recargar en el aparato. **Nunca lanza ni contesta un error al guardado**: sin aparato o sin
|
|
3916
|
+
* app, dice `sin-app` y ya. Con `probar` y la app sin correr, lanza el despliegue entero de
|
|
3917
|
+
* Ejecutar —que sube el proyecto entero— en vez de recargar lo que nadie tiene cargado.
|
|
3918
|
+
*/
|
|
3919
|
+
const atenderRecarga = async (raiz, ruta, { probar }) => {
|
|
3920
|
+
if (recargando) {
|
|
3921
|
+
siguienteRecarga = { raiz, ruta, probar };
|
|
3922
|
+
return;
|
|
3923
|
+
}
|
|
3924
|
+
recargando = true;
|
|
3925
|
+
try {
|
|
3926
|
+
const abierto = vestibulo.proyectoAbierto();
|
|
3927
|
+
if (abierto === undefined || abierto.raiz !== raiz)
|
|
3928
|
+
return;
|
|
3929
|
+
if (lanzamiento !== undefined) {
|
|
3930
|
+
emitirRecarga(ruta, { estado: "ocupado" });
|
|
3931
|
+
return;
|
|
3932
|
+
}
|
|
3933
|
+
const destino = await destinoDeRecarga(abierto);
|
|
3934
|
+
const recargar = opciones.recargarEnAparato;
|
|
3935
|
+
if ("motivo" in destino) {
|
|
3936
|
+
// Sin a dónde recargar tampoco hay a dónde desplegar: se dice por qué, y nada más.
|
|
3937
|
+
emitirRecarga(ruta, { estado: "sin-app", motivo: destino.motivo });
|
|
3938
|
+
return;
|
|
3939
|
+
}
|
|
3940
|
+
if (recargar === undefined) {
|
|
3941
|
+
emitirRecarga(ruta, { estado: "sin-app", motivo: "esta ejecución no recarga en el aparato" });
|
|
3942
|
+
return;
|
|
3943
|
+
}
|
|
3944
|
+
const resultado = await recargar(raiz, ruta, destino);
|
|
3945
|
+
if (resultado.estado === "sin-app" && probar) {
|
|
3946
|
+
emitirRecarga(ruta, { estado: "desplegando" });
|
|
3947
|
+
// Y cómo acabó, en la MISMA línea: sin esto se quedaba en «desplegando» para siempre.
|
|
3948
|
+
void atenderLanzarApp((estado, motivo) => {
|
|
3949
|
+
if (estado === "ok")
|
|
3950
|
+
emitirRecarga(ruta, { estado: "desplegada" });
|
|
3951
|
+
else if (estado === "ocupado")
|
|
3952
|
+
emitirRecarga(ruta, { estado: "ocupado" });
|
|
3953
|
+
else
|
|
3954
|
+
emitirRecarga(ruta, { estado: "fallo", motivo: `el despliegue no acabó bien: ${motivo ?? "sin motivo"}` });
|
|
3955
|
+
}).catch(contar);
|
|
3956
|
+
return;
|
|
3957
|
+
}
|
|
3958
|
+
emitirRecarga(ruta, resultado);
|
|
3959
|
+
}
|
|
3960
|
+
catch (error) {
|
|
3961
|
+
// Su contrato es no lanzar; si lo hace, se dice como fallo de la RECARGA, nunca del guardado.
|
|
3962
|
+
emitirRecarga(ruta, { estado: "fallo", motivo: motivoLegible(error) });
|
|
3963
|
+
}
|
|
3964
|
+
finally {
|
|
3965
|
+
recargando = false;
|
|
3966
|
+
const otra = siguienteRecarga;
|
|
3967
|
+
siguienteRecarga = undefined;
|
|
3968
|
+
if (otra !== undefined)
|
|
3969
|
+
void atenderRecarga(otra.raiz, otra.ruta, { probar: otra.probar }).catch(contar);
|
|
3970
|
+
}
|
|
3971
|
+
};
|
|
3972
|
+
/** Relanzar la app a petición (un CSS solo se ve así). Con las mismas reglas que la recarga. */
|
|
3973
|
+
const atenderRelanzar = async (ruta) => {
|
|
3974
|
+
const abierto = vestibulo.proyectoAbierto();
|
|
3975
|
+
if (abierto === undefined)
|
|
3976
|
+
return;
|
|
3977
|
+
if (lanzamiento !== undefined || recargando) {
|
|
3978
|
+
emitirRecarga(ruta, { estado: "ocupado" });
|
|
3979
|
+
return;
|
|
3980
|
+
}
|
|
3981
|
+
const destino = await destinoDeRecarga(abierto);
|
|
3982
|
+
const relanzar = opciones.relanzarEnAparato;
|
|
3983
|
+
if ("motivo" in destino || relanzar === undefined) {
|
|
3984
|
+
emitirRecarga(ruta, { estado: "sin-app", motivo: "motivo" in destino ? destino.motivo : "esta ejecución no relanza" });
|
|
3985
|
+
return;
|
|
3986
|
+
}
|
|
3987
|
+
recargando = true;
|
|
3988
|
+
try {
|
|
3989
|
+
emitirRecarga(ruta, await relanzar(destino));
|
|
3990
|
+
}
|
|
3991
|
+
catch (error) {
|
|
3992
|
+
emitirRecarga(ruta, { estado: "fallo", motivo: motivoLegible(error) });
|
|
3993
|
+
}
|
|
3994
|
+
finally {
|
|
3995
|
+
recargando = false;
|
|
3996
|
+
// Un guardado que llegó mientras se relanzaba quedó como siguiente: sin esto se perdía.
|
|
3997
|
+
const otra = siguienteRecarga;
|
|
3998
|
+
siguienteRecarga = undefined;
|
|
3999
|
+
if (otra !== undefined)
|
|
4000
|
+
void atenderRecarga(otra.raiz, otra.ruta, { probar: otra.probar }).catch(contar);
|
|
4001
|
+
}
|
|
4002
|
+
};
|
|
3852
4003
|
/**
|
|
3853
4004
|
* La base de las marcas del editor. Con la sesión ABIERTA (`abierto.sesion`, la misma que mira
|
|
3854
4005
|
* Revisión): su ref es la foto del inicio. Sin puerto se contesta `sinBase` —el editor funciona
|
|
@@ -5377,7 +5528,7 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
5377
5528
|
typeof mensaje.huella === "string" &&
|
|
5378
5529
|
typeof mensaje.id === "string" &&
|
|
5379
5530
|
typeof mensaje.proyecto === "string") {
|
|
5380
|
-
void atenderGuardarFichero(mensaje.ruta, mensaje.texto, mensaje.huella, mensaje.id, mensaje.proyecto).catch(contar);
|
|
5531
|
+
void atenderGuardarFichero(mensaje.ruta, mensaje.texto, mensaje.huella, mensaje.id, mensaje.proyecto, mensaje.recargar === true).catch(contar);
|
|
5381
5532
|
respuesta.writeHead(204);
|
|
5382
5533
|
respuesta.end();
|
|
5383
5534
|
return;
|
|
@@ -5879,6 +6030,22 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
5879
6030
|
respuesta.end();
|
|
5880
6031
|
return;
|
|
5881
6032
|
}
|
|
6033
|
+
if (typeof mensaje === "object" &&
|
|
6034
|
+
mensaje !== null &&
|
|
6035
|
+
mensaje.clase === "recargarEnAparato" &&
|
|
6036
|
+
typeof mensaje.ruta === "string") {
|
|
6037
|
+
// Suelto: habla con el aparato, y la respuesta va por el SSE como `recargaEnAparato`.
|
|
6038
|
+
const abierto = vestibulo.proyectoAbierto();
|
|
6039
|
+
if (abierto !== undefined) {
|
|
6040
|
+
if (mensaje.relanzar === true)
|
|
6041
|
+
void atenderRelanzar(mensaje.ruta).catch(contar);
|
|
6042
|
+
else
|
|
6043
|
+
void atenderRecarga(abierto.raiz, mensaje.ruta, { probar: mensaje.probar === true }).catch(contar);
|
|
6044
|
+
}
|
|
6045
|
+
respuesta.writeHead(204);
|
|
6046
|
+
respuesta.end();
|
|
6047
|
+
return;
|
|
6048
|
+
}
|
|
5882
6049
|
if (typeof mensaje === "object" && mensaje !== null && mensaje.clase === "cancelarLanzamiento") {
|
|
5883
6050
|
// Este SÍ es síncrono: `cancelar` solo mata el grupo del proceso en curso.
|
|
5884
6051
|
atenderCancelarLanzamiento();
|
|
@@ -6486,6 +6653,17 @@ export function ficherosCableados() {
|
|
|
6486
6653
|
* (`guardarAjusteDeAvd(undefined, …)`). `arrancar` entra solo para que un test vea qué recibe la
|
|
6487
6654
|
* de verdad sin lanzar un emulador.
|
|
6488
6655
|
*/
|
|
6656
|
+
/**
|
|
6657
|
+
* La recarga en caliente del editor, con las funciones de VERDAD y compuesta fuera del cableado
|
|
6658
|
+
* por la trampa de siempre: un campo opcional olvidado en `arrancarConsolaWeb` deja la casilla
|
|
6659
|
+
* pintada y sin hacer nada, con todo en verde. El `adb` y el canal reales los ponen ellas.
|
|
6660
|
+
*/
|
|
6661
|
+
export function recargaCableada() {
|
|
6662
|
+
return {
|
|
6663
|
+
recargarEnAparato: (raiz, ruta, destino) => recargarFicheroDelProyecto(raiz, ruta, destino),
|
|
6664
|
+
relanzarEnAparato: (destino) => relanzarEnAparato(destino),
|
|
6665
|
+
};
|
|
6666
|
+
}
|
|
6489
6667
|
export function emuladoresCableados(opciones = {}) {
|
|
6490
6668
|
const arrancar = opciones.arrancar ?? arrancarEmulador;
|
|
6491
6669
|
return {
|
|
@@ -7048,6 +7226,7 @@ export async function arrancarConsolaWeb(opciones) {
|
|
|
7048
7226
|
* arriba: una composición de producción que solo existe en el cableado nadie la prueba.
|
|
7049
7227
|
*/
|
|
7050
7228
|
lanzarEnDispositivo,
|
|
7229
|
+
...recargaCableada(),
|
|
7051
7230
|
catalogoDeModelos: async (proveedor) => {
|
|
7052
7231
|
const modelos = await new CatalogoModelos(undefined, undefined, proveedoresPersonalizados).listar(proveedor);
|
|
7053
7232
|
return modelos.map((m) => ({ id: m.id, ...(m.nombre === undefined ? {} : { nombre: m.nombre }) }));
|