xonecode 0.26.1 → 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-BW6i19HW.js → EditorDeFichero-ChyJtiiz.js} +1 -1
- package/apps/web/dist/assets/{index-CJycg35E.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-DxWUQTjM.js → php-B91ZcS-9.js} +1 -1
- package/apps/web/dist/assets/{ruby-gCKsUWxP.js → ruby-DrxrTSz0.js} +1 -1
- package/apps/web/dist/index.html +2 -2
- 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/core/codificacion.js +134 -0
- package/dist/core/codificacion.js.map +1 -0
- package/dist/core/recargaEnCaliente.js +114 -0
- package/dist/core/recargaEnCaliente.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,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* La codificación de un fichero de texto del proyecto, en las dos direcciones, con UNA tabla por
|
|
3
|
+
* juego de caracteres.
|
|
4
|
+
*
|
|
5
|
+
* Los proyectos XOne traen sus `.xne` en UTF-8 o en ISO-8859 (`<?xml … encoding="iso-8859-15"?>`,
|
|
6
|
+
* lo que emiten las plantillas de la skill). XOne los lee según ese `encoding=`, así que guardarlos
|
|
7
|
+
* en otra codificación rompería cada tilde sin un error que leer: un bug mudo, y el mismo que la
|
|
8
|
+
* skill nombra como «encoding incoherente». Lo que hace falta es escribir en la codificación que el
|
|
9
|
+
* fichero YA tenía, o la que DECLARA si todavía no la delatan sus bytes.
|
|
10
|
+
*
|
|
11
|
+
* Dos tablas de 8 bits, y se elige UNA por fichero en las dos direcciones —leer con una y escribir
|
|
12
|
+
* con otra movería caracteres—:
|
|
13
|
+
* - `iso-8859-15` si el prólogo lo declara: el € en 0xA4.
|
|
14
|
+
* - `windows-1252` en todo lo demás (lo que `TextDecoder("latin1")` es en WHATWG): el € en 0x80, y
|
|
15
|
+
* los cinco bytes que windows-1252 no define salen como el control C1 del mismo número.
|
|
16
|
+
* Cada codificador se CONSTRUYE invirtiendo su decodificador byte a byte, no se copia de una tabla:
|
|
17
|
+
* así la ida y vuelta es exacta por construcción, C1 incluidos.
|
|
18
|
+
*
|
|
19
|
+
* Puro: `Uint8Array` y `TextDecoder`, sin `Buffer` ni disco.
|
|
20
|
+
*/
|
|
21
|
+
function construir(etiqueta) {
|
|
22
|
+
const decodificador = new TextDecoder(etiqueta);
|
|
23
|
+
const byteDe = new Map();
|
|
24
|
+
for (let byte = 0; byte < 256; byte++) {
|
|
25
|
+
byteDe.set(decodificador.decode(Uint8Array.of(byte)).codePointAt(0), byte);
|
|
26
|
+
}
|
|
27
|
+
return { decodificar: (bytes) => decodificador.decode(bytes), byteDe };
|
|
28
|
+
}
|
|
29
|
+
/** Se construyen una vez, al cargar el módulo. `latin1` es la etiqueta que usaba ya la lectura. */
|
|
30
|
+
const TABLAS = {
|
|
31
|
+
"windows-1252": construir("latin1"),
|
|
32
|
+
"iso-8859-15": construir("iso-8859-15"),
|
|
33
|
+
};
|
|
34
|
+
/** Bytes de 8 bits → texto, con la tabla del fichero. */
|
|
35
|
+
export function decodificar8Bits(bytes, tabla) {
|
|
36
|
+
return TABLAS[tabla].decodificar(bytes);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Texto → bytes de 8 bits, o el PRIMER carácter que no cabe. Nunca se sustituye por `?`: un `?` en
|
|
40
|
+
* lugar de una letra es otra forma del bug mudo. Se recorre por CODE POINT, así que un emoji (dos
|
|
41
|
+
* unidades UTF-16) se nombra entero.
|
|
42
|
+
*/
|
|
43
|
+
export function codificar8Bits(texto, tabla) {
|
|
44
|
+
const byteDe = TABLAS[tabla].byteDe;
|
|
45
|
+
const bytes = new Uint8Array(texto.length);
|
|
46
|
+
let n = 0;
|
|
47
|
+
let linea = 1;
|
|
48
|
+
for (const caracter of texto) {
|
|
49
|
+
const byte = byteDe.get(caracter.codePointAt(0));
|
|
50
|
+
if (byte === undefined)
|
|
51
|
+
return { caracter, linea };
|
|
52
|
+
if (caracter === "\n")
|
|
53
|
+
linea++;
|
|
54
|
+
bytes[n++] = byte;
|
|
55
|
+
}
|
|
56
|
+
return { bytes: bytes.subarray(0, n) };
|
|
57
|
+
}
|
|
58
|
+
/** Cómo se NOMBRA la tabla en un mensaje para una persona o para el agente. */
|
|
59
|
+
export function nombreDeTabla(tabla) {
|
|
60
|
+
return tabla === "iso-8859-15" ? "ISO-8859-15" : "Latin-1";
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* La codificación que DECLARA el prólogo XML, mirada como la mira `xone-linter`
|
|
64
|
+
* (`XmlParser.js#detectEncoding`): la cabeza del texto, `<?xml … encoding="…"`, y «8859-15» antes
|
|
65
|
+
* que «8859-1» (que lo contiene). Lo que no es un prólogo al principio —sin contar el BOM y los
|
|
66
|
+
* blancos— no declara nada, y una codificación que no sabemos escribir tampoco.
|
|
67
|
+
*/
|
|
68
|
+
export function codificacionDeclarada(cabeza) {
|
|
69
|
+
const m = /^?\s*<\?xml[^?]*?encoding\s*=\s*["']([^"']+)["']/i.exec(cabeza.slice(0, 256));
|
|
70
|
+
if (m === null)
|
|
71
|
+
return undefined;
|
|
72
|
+
const declarada = m[1].toLowerCase();
|
|
73
|
+
if (declarada.includes("8859-15") || declarada === "latin-9" || declarada === "latin9")
|
|
74
|
+
return "iso-8859-15";
|
|
75
|
+
if (declarada.includes("8859-1") || declarada === "latin1" || declarada === "latin-1" || declarada.includes("1252")) {
|
|
76
|
+
return "windows-1252";
|
|
77
|
+
}
|
|
78
|
+
if (declarada === "utf-8" || declarada === "utf8")
|
|
79
|
+
return "utf-8";
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
/** La tabla de 8 bits de unos bytes que NO son UTF-8: la que declare su prólogo, o windows-1252. */
|
|
83
|
+
export function tablaDeLosBytes(bytes) {
|
|
84
|
+
// La cabeza se mira byte a byte (como `latin1`): el prólogo es ASCII en cualquier tabla.
|
|
85
|
+
const cabeza = decodificar8Bits(bytes.subarray(0, 256), "windows-1252");
|
|
86
|
+
return codificacionDeclarada(cabeza) === "iso-8859-15" ? "iso-8859-15" : "windows-1252";
|
|
87
|
+
}
|
|
88
|
+
/** UTF-8 ESTRICTO, el mismo criterio que la lectura para decidir que un fichero no lo es. */
|
|
89
|
+
export function esUtf8Valido(bytes) {
|
|
90
|
+
try {
|
|
91
|
+
new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function esAscii(bytes) {
|
|
99
|
+
return bytes.every((b) => b < 0x80);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* En qué bytes se guarda `texto` sobre lo que hay (`actual`, ausente si el fichero es NUEVO). En
|
|
103
|
+
* orden:
|
|
104
|
+
* 1. Lo que ya no es UTF-8 sigue en 8 bits: con la tabla que declare lo que se escribe, si declara
|
|
105
|
+
* una de 8 bits (la que usará la próxima lectura), o si no con la de sus bytes (`tablaDeLosBytes`).
|
|
106
|
+
* 2. Un UTF-8 con algo que no es ASCII se queda en UTF-8: nunca se transcodifica lo que nadie tocó.
|
|
107
|
+
* 3. Un ASCII puro —que es UTF-8 válido y no delata nada— o un fichero nuevo siguen lo que DECLARE el
|
|
108
|
+
* texto que se escribe; sin declaración, UTF-8. Es el caso de toda colección nueva con la
|
|
109
|
+
* plantilla de la skill (`encoding="iso-8859-15"`): sin esto, su primera tilde iba en UTF-8 bajo
|
|
110
|
+
* una declaración de 8 bits.
|
|
111
|
+
*/
|
|
112
|
+
export function codificacionParaEscribir(actual, texto) {
|
|
113
|
+
if (actual !== undefined && !esUtf8Valido(actual)) {
|
|
114
|
+
// Si lo que se escribe CAMBIA la declaración a otra tabla de 8 bits, manda la nueva: es con la
|
|
115
|
+
// que se volverá a leer, y escribir con la vieja dejaría un € en 0x80 leído luego como control.
|
|
116
|
+
const declarada = codificacionDeclarada(texto);
|
|
117
|
+
return declarada !== undefined && declarada !== "utf-8" ? declarada : tablaDeLosBytes(actual);
|
|
118
|
+
}
|
|
119
|
+
if (actual !== undefined && !esAscii(actual))
|
|
120
|
+
return "utf-8";
|
|
121
|
+
return codificacionDeclarada(texto) ?? "utf-8";
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Bytes de un texto del proyecto → el texto que ve el AGENTE: UTF-8 si lo es (con el BOM dentro,
|
|
125
|
+
* como `readFile(…, "utf8")`, que es lo que hace la librería), y si no, su tabla de 8 bits. Es lo
|
|
126
|
+
* que tiene que leer cualquiera que compare contra lo que el agente leyó —el ANTES de la tarjeta de
|
|
127
|
+
* aprobación—: con UTF-8 a secas, un ancla con tilde no calzaba y cada línea con tilde salía cambiada.
|
|
128
|
+
*/
|
|
129
|
+
export function decodificarComoTexto(bytes) {
|
|
130
|
+
return esUtf8Valido(bytes)
|
|
131
|
+
? new TextDecoder("utf-8", { ignoreBOM: true }).decode(bytes)
|
|
132
|
+
: decodificar8Bits(bytes, tablaDeLosBytes(bytes));
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=codificacion.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codificacion.js","sourceRoot":"","sources":["../../src/core/codificacion.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Qué hace el aparato con un fichero recién guardado en el editor: la regla de la recarga en
|
|
3
|
+
* caliente, pura.
|
|
4
|
+
*
|
|
5
|
+
* La regla es la de `skills/xone-hotswap/scripts/xone-recargar-android`, MEDIDA contra el
|
|
6
|
+
* framework 5.0.5.5dev, y no se inventa una nueva:
|
|
7
|
+
*
|
|
8
|
+
* colección (`.xne` con `<coll`) → `uploadFile` + `loadCollection`: se repinta en el acto. Solo
|
|
9
|
+
* en memoria, y por eso se sube también a disco — sin eso,
|
|
10
|
+
* relanzar la app la devuelve a la versión anterior.
|
|
11
|
+
* JavaScript (`.js`) → `uploadFile` + `loadIncludeFile` con `compile: true`.
|
|
12
|
+
* CSS, `.ini`, `app.xml`, un `.xne` sin colección (mappings)
|
|
13
|
+
* → `uploadFile`, y hace falta RELANZAR la app. Medido:
|
|
14
|
+
* `setCssAttribute`, `relayout` y `refresh` contestan
|
|
15
|
+
* `result:true` y no cambian nada.
|
|
16
|
+
* todo lo demás (iconos, `bd/`, `files/`) → no se recarga: lo lleva el despliegue entero.
|
|
17
|
+
*
|
|
18
|
+
* **Relanzar no se hace solo**: se pierde el estado de la pantalla en que está la persona, así
|
|
19
|
+
* que lo que se devuelve es «hace falta relanzar» y la pantalla lo OFRECE.
|
|
20
|
+
*/
|
|
21
|
+
import { decodificarComoTexto, esUtf8Valido, tablaDeLosBytes } from "./codificacion.js";
|
|
22
|
+
/** Las extensiones que el aparato sabe tomar sueltas. Las mismas del script. */
|
|
23
|
+
const EXTENSIONES = new Set([".xne", ".js", ".css", ".ini", ".xml"]);
|
|
24
|
+
/** Carpetas que nunca viajan sueltas. Las mismas del script. */
|
|
25
|
+
const FUERA = ["bd", "files", "icons"];
|
|
26
|
+
/** Una colección se declara con `<coll`: lo mismo que mira el script, sobre la cabeza del texto. */
|
|
27
|
+
const DECLARA_COLECCION = /<coll[\s>/]/;
|
|
28
|
+
function extension(ruta) {
|
|
29
|
+
const nombre = ruta.slice(ruta.lastIndexOf("/") + 1);
|
|
30
|
+
const punto = nombre.lastIndexOf(".");
|
|
31
|
+
return punto <= 0 ? "" : nombre.slice(punto).toLowerCase();
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Cómo se aplica `ruta` (relativa, con `/`) con estos `bytes`. El texto se mira byte a byte: la
|
|
35
|
+
* etiqueta `<coll` es ASCII en cualquier tabla.
|
|
36
|
+
*/
|
|
37
|
+
export function claseDeRecarga(ruta, bytes) {
|
|
38
|
+
const limpia = ruta.replace(/^\/+/, "");
|
|
39
|
+
if (FUERA.some((f) => limpia === f || limpia.startsWith(`${f}/`)))
|
|
40
|
+
return "no-aplica";
|
|
41
|
+
const ext = extension(limpia);
|
|
42
|
+
if (!EXTENSIONES.has(ext))
|
|
43
|
+
return "no-aplica";
|
|
44
|
+
if (ext === ".js")
|
|
45
|
+
return "js";
|
|
46
|
+
if (ext === ".xne") {
|
|
47
|
+
let cabeza = "";
|
|
48
|
+
for (const b of bytes)
|
|
49
|
+
cabeza += String.fromCharCode(b);
|
|
50
|
+
return DECLARA_COLECCION.test(cabeza) ? "coleccion" : "relanzar";
|
|
51
|
+
}
|
|
52
|
+
return "relanzar";
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* El nombre de la codificación que se le dice al framework para que lea ESTOS bytes. Es la misma
|
|
56
|
+
* regla que la lectura del agente y del editor (`core/codificacion.ts`): UTF-8 si lo es; si no, la
|
|
57
|
+
* tabla de 8 bits que declare el prólogo (ISO-8859-15) o windows-1252. Mandar `UTF-8` a secas
|
|
58
|
+
* —lo que hace el script— enseñaba en el aparato un «�» en cada tilde de un `.xne` Latin-1.
|
|
59
|
+
* Los nombres son los de `java.nio.charset`, que es quien los lee al otro lado.
|
|
60
|
+
*/
|
|
61
|
+
export function codificacionParaElAparato(bytes) {
|
|
62
|
+
if (esUtf8Valido(bytes))
|
|
63
|
+
return "UTF-8";
|
|
64
|
+
return tablaDeLosBytes(bytes) === "iso-8859-15" ? "ISO-8859-15" : "windows-1252";
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Lo que se le da a `loadCollection`: el MISMO XML, en UTF-8 y con el prólogo diciendo UTF-8.
|
|
68
|
+
*
|
|
69
|
+
* MEDIDO en el emulador (framework 5.0.5.5dev) con un `.xne` en ISO-8859-15 y «Año ñá»: con sus
|
|
70
|
+
* bytes y `encoding: "ISO-8859-15"` el aparato pintaba «Año ñá» —y estropeaba también el
|
|
71
|
+
* «Contraseña» que ya estaba—; con el texto en UTF-8 y el prólogo cambiado a UTF-8 se ve bien. El
|
|
72
|
+
* framework decodifica con `encoding` y su analizador vuelve a leer el prólogo: si los dos no dicen
|
|
73
|
+
* lo mismo que los bytes, cada tilde sale doble. Solo para la copia EN MEMORIA: lo que se sube a
|
|
74
|
+
* disco (`uploadFile`) son los bytes tal cual, que es lo que el framework lee al relanzar.
|
|
75
|
+
*/
|
|
76
|
+
export function xmlParaCargarEnMemoria(bytes) {
|
|
77
|
+
const texto = decodificarComoTexto(bytes).replace(/^\uFEFF/, "");
|
|
78
|
+
const conPrologoUtf8 = texto.replace(/^(\s*<\?xml[^?]*?encoding\s*=\s*)(["'])[^"']*\2/i, (_todo, antes, comilla) => `${antes}${comilla}UTF-8${comilla}`);
|
|
79
|
+
return new TextEncoder().encode(conPrologoUtf8);
|
|
80
|
+
}
|
|
81
|
+
/** Dónde guarda el framework lo que se le sube: `app_<nombre en minúsculas>/<ruta>` (medido). */
|
|
82
|
+
export function destinoEnElAparato(app, ruta) {
|
|
83
|
+
return `app_${app.toLowerCase()}/${ruta.replace(/^\/+/, "")}`;
|
|
84
|
+
}
|
|
85
|
+
/** La frase que enseña la cabecera del editor. Pura para que el test la fije. */
|
|
86
|
+
export function textoDeRecarga(r) {
|
|
87
|
+
switch (r.estado) {
|
|
88
|
+
case "aplicada":
|
|
89
|
+
return r.clase === "js"
|
|
90
|
+
? "Aparato: JavaScript recargado; la app sigue viva"
|
|
91
|
+
: "Aparato: pantalla recargada en caliente; la app sigue viva";
|
|
92
|
+
case "relanzar":
|
|
93
|
+
return "Aparato: subido; para verlo hay que relanzar la app";
|
|
94
|
+
case "relanzada":
|
|
95
|
+
return "Aparato: app relanzada y viva";
|
|
96
|
+
case "sin-app":
|
|
97
|
+
// El motivo, si se sabe: «no hay aparato elegido» y «no está en la medida» no se arreglan
|
|
98
|
+
// en el mismo sitio que «la app no corre».
|
|
99
|
+
return r.motivo !== undefined && !r.motivo.includes("App is not running")
|
|
100
|
+
? `Aparato: no se recarga (${r.motivo})`
|
|
101
|
+
: "Aparato: la app no está corriendo; lánzala desde Ejecutar o pulsa Probar";
|
|
102
|
+
case "desplegada":
|
|
103
|
+
return "Aparato: proyecto desplegado y la app viva";
|
|
104
|
+
case "desplegando":
|
|
105
|
+
return "Aparato: la app no corría; desplegando el proyecto (el recorrido, en Ejecutar)";
|
|
106
|
+
case "ocupado":
|
|
107
|
+
return "Aparato: hay un despliegue en curso; no se recarga encima";
|
|
108
|
+
case "no-aplica":
|
|
109
|
+
return "Aparato: este fichero no se recarga suelto; hace falta desplegar entero";
|
|
110
|
+
case "fallo":
|
|
111
|
+
return `Aparato: no se pudo recargar (${r.motivo ?? "sin motivo"})`;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=recargaEnCaliente.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recargaEnCaliente.js","sourceRoot":"","sources":["../../src/core/recargaEnCaliente.ts"],"names":[],"mappings":"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"}
|
|
@@ -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 }) }));
|