xonecode 0.38.4 → 0.38.11

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.
Files changed (35) hide show
  1. package/apps/web/dist/assets/{EditorDeFichero-ClGfUHp0.js → EditorDeFichero-C_95A4sB.js} +1 -1
  2. package/apps/web/dist/assets/index-CAqQp65A.js +506 -0
  3. package/apps/web/dist/assets/index-tzWrUZYU.css +1 -0
  4. package/apps/web/dist/assets/{php-DFC2MqmC.js → php-CkVQ0jw6.js} +1 -1
  5. package/apps/web/dist/assets/{ruby-BfhTj9bK.js → ruby-m3xVWkBq.js} +1 -1
  6. package/apps/web/dist/index.html +2 -2
  7. package/dist/agent/config/settingsEnDisco.js +44 -4
  8. package/dist/agent/config/settingsEnDisco.js.map +1 -1
  9. package/dist/agent/tareas/tareasEnDisco.js +23 -1
  10. package/dist/agent/tareas/tareasEnDisco.js.map +1 -1
  11. package/dist/core/dispositivoDeSesion.js +2 -7
  12. package/dist/core/dispositivoDeSesion.js.map +1 -1
  13. package/dist/core/dispositivoElegido.js +22 -0
  14. package/dist/core/dispositivoElegido.js.map +1 -0
  15. package/dist/core/puertosDeAvd.js +53 -8
  16. package/dist/core/puertosDeAvd.js.map +1 -1
  17. package/dist/core/settings.js +21 -1
  18. package/dist/core/settings.js.map +1 -1
  19. package/dist/core/tareas.js +29 -0
  20. package/dist/core/tareas.js.map +1 -1
  21. package/dist/web/servidor/arranque.js +192 -12
  22. package/dist/web/servidor/arranque.js.map +1 -1
  23. package/dist/web/servidor/corredorDeTareas.js +27 -5
  24. package/dist/web/servidor/corredorDeTareas.js.map +1 -1
  25. package/dist/web/servidor/transporte.js.map +1 -1
  26. package/dist/web/servidor/vestibulo.js +26 -1
  27. package/dist/web/servidor/vestibulo.js.map +1 -1
  28. package/package.json +1 -1
  29. package/skills/xone-hotswap/SKILL.md +4 -2
  30. package/skills/xone-hotswap/lib/dispositivo.mjs +106 -1
  31. package/skills/xone-hotswap/scripts/xone-desplegar-android +3 -1
  32. package/skills/xone-hotswap/scripts/xone-hotswap +4 -1
  33. package/skills/xone-hotswap/scripts/xone-reiniciar-android +3 -1
  34. package/apps/web/dist/assets/index-BKbTb9C_.js +0 -506
  35. package/apps/web/dist/assets/index-QE7QDed9.css +0 -1
@@ -179,9 +179,55 @@ export function ajusteDelAvd(avd, { casa = homedir(), leer = (r) => readFileSync
179
179
  }
180
180
  }
181
181
 
182
+ /** La forma de un serial de adb. Copia de `core/puertosDeAvd.ts#FORMA_DE_SERIE`. */
183
+ export const FORMA_DE_SERIE = /^[A-Za-z0-9._:-]{1,128}$/;
184
+
185
+ const puertoValido = (p) => Number.isInteger(p) && p >= 1024 && p <= 65535;
186
+
187
+ /** `dispositivos` de `~/.xonecode/settings.json`, o `{}`. Se lee en CADA ejecución, como el resto. */
188
+ function ajustesDeDispositivos({ casa = homedir(), leer = (r) => readFileSync(r, "utf8") } = {}) {
189
+ try {
190
+ const d = JSON.parse(leer(join(casa, ".xonecode", "settings.json")))?.dispositivos;
191
+ return typeof d === "object" && d !== null ? d : {};
192
+ } catch {
193
+ return {};
194
+ }
195
+ }
196
+
197
+ /** Los puertos ya guardados, de AVD y de físicos, con el MISMO filtro que `core/settings.ts`. */
198
+ function puertosGuardados(d) {
199
+ const usados = new Set();
200
+ const de = (tabla, forma) => {
201
+ if (typeof tabla !== "object" || tabla === null) return;
202
+ for (const [clave, valor] of Object.entries(tabla)) {
203
+ if (forma.test(clave) && typeof valor === "object" && valor !== null && puertoValido(valor.puerto)) usados.add(valor.puerto);
204
+ }
205
+ };
206
+ de(d.avds, FORMA_DE_NOMBRE_DE_AVD);
207
+ de(d.fisicos, FORMA_DE_SERIE);
208
+ return usados;
209
+ }
210
+
211
+ /**
212
+ * El puerto de un móvil FÍSICO (IXCODE-32): el guardado para su serie o, sin él, el primero libre
213
+ * desde el 8443. Antes era siempre el 8443, el mismo que el primer AVD, y el último `adb forward`
214
+ * se quedaba el puerto: la sesión del móvil hablaba con el emulador sin error. Copia de
215
+ * `core/puertosDeAvd.ts#puertoDeFisico` (un test compara las dos).
216
+ */
217
+ export function puertoDeFisico(serie, deps = {}) {
218
+ const d = ajustesDeDispositivos(deps);
219
+ const guardado = FORMA_DE_SERIE.test(serie ?? "") ? d.fisicos?.[serie]?.puerto : undefined;
220
+ if (puertoValido(guardado)) return guardado;
221
+ const usados = puertosGuardados(d);
222
+ let candidato = PUERTO_DEL_HOTSWAP;
223
+ while (usados.has(candidato)) candidato++;
224
+ return candidato;
225
+ }
226
+
182
227
  /**
183
228
  * El puerto LOCAL del túnel. De más explícito a menos: `--puerto`, el guardado del AVD de esta
184
- * serie, 8443. Misma regla que `core/puertosDeAvd.ts#puertoDeAvd` (un test compara las dos).
229
+ * serie (o el del físico), 8443. Misma regla que `core/puertosDeAvd.ts#puertoDeDispositivo`
230
+ * (un test compara las dos).
185
231
  */
186
232
  export function puertoAndroid(explicito, serie, deps = {}) {
187
233
  return puertoConMotivo(explicito, serie, deps).puerto;
@@ -196,6 +242,7 @@ export function puertoAndroid(explicito, serie, deps = {}) {
196
242
  export function puertoConMotivo(explicito, serie, deps = {}) {
197
243
  const n = Number(explicito);
198
244
  if (explicito !== undefined && Number.isInteger(n) && n >= 1024 && n <= 65535) return { puerto: n };
245
+ if (serie !== undefined && !serie.startsWith("emulator-")) return { puerto: puertoDeFisico(serie, deps) };
199
246
  const avd = avdDeLaSerie(serie, deps);
200
247
  const guardado = ajusteDelAvd(avd, deps).puerto;
201
248
  if (guardado !== undefined) return { puerto: guardado };
@@ -205,6 +252,64 @@ export function puertoConMotivo(explicito, serie, deps = {}) {
205
252
  return { puerto: PUERTO_DEL_HOTSWAP };
206
253
  }
207
254
 
255
+ /** De la salida de `adb forward --list`, la serie que tiene el puerto local `tcp:<puerto>`, o `undefined`. Pura. */
256
+ export function duenoDelPuerto(salidaDeForwardList, puerto) {
257
+ for (const linea of String(salidaDeForwardList).split(/\r?\n/)) {
258
+ const [serie, local] = linea.trim().split(/\s+/);
259
+ if (local === `tcp:${puerto}` && serie) return serie;
260
+ }
261
+ return undefined;
262
+ }
263
+
264
+ /**
265
+ * ¿De quién es AHORA el túnel de este puerto? (IXCODE-32). La tabla de `adb forward` es de toda la
266
+ * máquina: si otra sesión, una tarea o una consola le hizo un forward a OTRO aparato en el mismo
267
+ * puerto, los comandos de ésta irían a ese aparato sin error y el script seguiría diciendo
268
+ * «dispositivo: el de la sesión». Comprobar la SERIE no basta; hay que comprobar el puerto.
269
+ *
270
+ * `propio` (es suyo), `libre` (nadie lo tiene), `ajeno` (con `dueno`) o `desconocido` (adb no
271
+ * contestó: no se puede afirmar nada y quien llama sigue como antes).
272
+ */
273
+ export function estadoDelTunel(serie, puerto, { entorno = process.env, ejecutar = ejecutarAdb } = {}) {
274
+ let salida;
275
+ try {
276
+ salida = ejecutar(rutaDeAdb(entorno), ["forward", "--list"]);
277
+ } catch {
278
+ return { estado: "desconocido" };
279
+ }
280
+ const dueno = duenoDelPuerto(salida, puerto);
281
+ if (dueno === undefined) return { estado: "libre" };
282
+ return dueno === serie ? { estado: "propio" } : { estado: "ajeno", dueno };
283
+ }
284
+
285
+ /**
286
+ * Comprueba el túnel antes de usarlo, y lo pone si NADIE lo tiene: sale con 2 si es de otro
287
+ * aparato, y con adb mudo no afirma nada. Devuelve el estado que encontró. `poner` es falso para
288
+ * quien va a poner el túnel él mismo justo después (desplegar, reiniciar).
289
+ */
290
+ export function asegurarTunel(serie, puerto, { entorno = process.env, ejecutar = ejecutarAdb, negar = negarYSalir, poner = true } = {}) {
291
+ const t = estadoDelTunel(serie, puerto, { entorno, ejecutar });
292
+ if (t.estado === "ajeno") return negar(textoDeTunelAjeno(serie, puerto, t.dueno));
293
+ if (t.estado === "libre" && poner) {
294
+ try {
295
+ ejecutar(rutaDeAdb(entorno), ["-s", serie, "forward", `tcp:${puerto}`, `tcp:${PUERTO_DEL_HOTSWAP}`]);
296
+ process.stderr.write(`túnel: no estaba puesto; ahora 127.0.0.1:${puerto} → ${serie}\n`);
297
+ } catch {
298
+ // Sin túnel la conexión fallará y lo dirá; aquí no hay nada más que afirmar.
299
+ }
300
+ }
301
+ return t;
302
+ }
303
+
304
+ /** Lo que se dice cuando el puerto lo tiene otro aparato: cuál, y que no se le quita. */
305
+ export function textoDeTunelAjeno(serie, puerto, dueno) {
306
+ return (
307
+ `El túnel 127.0.0.1:${puerto} lleva a ${dueno}, no a ${serie}: otra sesión, una tarea o un adb forward a mano ` +
308
+ "se lo quedó. No se le quita, porque dejaría al otro hablando con este aparato. " +
309
+ "Comprueba los puertos en Ajustes → Dispositivos o, si ese aparato ya no se usa, desconéctalo."
310
+ );
311
+ }
312
+
208
313
  /**
209
314
  * Retira de los argumentos las opciones que llevan valor (`--puerto 9000`), estén donde estén, y
210
315
  * deja el resto INTACTO (incluidos `--` y los `clave=valor`). Solo quita lo que ESTÁ: con un
@@ -49,7 +49,7 @@ import { execFileSync } from "node:child_process";
49
49
  import { existsSync, readFileSync, readdirSync } from "node:fs";
50
50
  import { basename, join, relative, sep } from "node:path";
51
51
  import { deflateRawSync } from "node:zlib";
52
- import { serieAndroid, rutaDeAdb, NODE, dormir, PUERTO_DEL_HOTSWAP, puertoConMotivo } from "../lib/dispositivo.mjs";
52
+ import { serieAndroid, rutaDeAdb, NODE, dormir, PUERTO_DEL_HOTSWAP, puertoConMotivo, asegurarTunel } from "../lib/dispositivo.mjs";
53
53
 
54
54
  const PAQUETE = "com.xone.android.framework";
55
55
  const ACTIVIDAD = `${PAQUETE}/com.xone.android.hotswap.activities.SetupActivity`;
@@ -353,6 +353,8 @@ function exigirAparato() {
353
353
 
354
354
  exigirAparato();
355
355
  console.log(`1/5 túnel: 127.0.0.1:${PUERTO} → ${PUERTO_DEL_HOTSWAP} del aparato`);
356
+ // Antes de ponerlo, que no sea de OTRO aparato (IXCODE-32): quitárselo dejaría al otro hablando con éste.
357
+ if (serie !== undefined) asegurarTunel(serie, PUERTO, { poner: false });
356
358
  adb("forward", `tcp:${PUERTO}`, `tcp:${PUERTO_DEL_HOTSWAP}`);
357
359
 
358
360
  console.log("2/5 empaquetando el proyecto (sin .xonecode ni .git)");
@@ -56,7 +56,7 @@ process.on("warning", (aviso) => {
56
56
  import { mkdirSync, writeFileSync } from "node:fs";
57
57
  import { join } from "node:path";
58
58
  import { guardarGeometria } from "../lib/geometria.mjs";
59
- import { dispositivoDeLaSesion, puertoConMotivo, retirarOpciones, serieAndroid, PUERTO_DEL_HOTSWAP } from "../lib/dispositivo.mjs";
59
+ import { asegurarTunel, dispositivoDeLaSesion, puertoConMotivo, retirarOpciones, serieAndroid, PUERTO_DEL_HOTSWAP } from "../lib/dispositivo.mjs";
60
60
 
61
61
  /**
62
62
  * La URL cuando nadie dice `HOTSWAP_URL`: el puerto LOCAL del túnel es del aparato, no un 8443
@@ -69,6 +69,9 @@ function urlPorOmision(puertoPedido, seriePedida) {
69
69
  const { serie, porque } = serieAndroid(seriePedida);
70
70
  const { puerto, porque: porquePuerto } = puertoConMotivo(puertoPedido, serie);
71
71
  if (porquePuerto !== undefined) process.stderr.write(`puerto: ${puerto} (${porquePuerto})\n`);
72
+ // El puerto puede ser de OTRO aparato aunque la serie sea la buena (IXCODE-32): la tabla de
73
+ // `adb forward` es de toda la máquina. Se comprueba antes de hablar; con `--puerto` manda quien lo pasó.
74
+ if (serie !== undefined && puertoPedido === undefined) asegurarTunel(serie, puerto);
72
75
  return `wss://127.0.0.1:${puerto}/hotswap`;
73
76
  }
74
77
  const TOPE_MS = Number(process.env.HOTSWAP_TOPE_MS ?? 40_000);
@@ -22,7 +22,7 @@
22
22
  */
23
23
  import { execFileSync } from "node:child_process";
24
24
  import { join } from "node:path";
25
- import { serieAndroid, rutaDeAdb, NODE, dormir, PUERTO_DEL_HOTSWAP, puertoConMotivo } from "../lib/dispositivo.mjs";
25
+ import { serieAndroid, rutaDeAdb, NODE, dormir, PUERTO_DEL_HOTSWAP, puertoConMotivo, asegurarTunel } from "../lib/dispositivo.mjs";
26
26
 
27
27
  const PAQUETE = "com.xone.android.framework";
28
28
  const ACTIVIDAD = `${PAQUETE}/com.xone.android.hotswap.activities.SetupActivity`;
@@ -102,6 +102,8 @@ exigirAparato();
102
102
  console.log(`1/3 túnel (127.0.0.1:${PUERTO} → ${PUERTO_DEL_HOTSWAP} del aparato) y parada de ${PAQUETE}`);
103
103
  // El túnel PRIMERO y sin quitarlo después: hay que reaplicarlo tras cada reconexión del cable,
104
104
  // así que quitarlo dejaría al siguiente hablando con nadie.
105
+ // Antes de ponerlo, que no sea de OTRO aparato (IXCODE-32): quitárselo dejaría al otro hablando con éste.
106
+ if (serie !== undefined) asegurarTunel(serie, PUERTO, { poner: false });
105
107
  adb("forward", `tcp:${PUERTO}`, `tcp:${PUERTO_DEL_HOTSWAP}`);
106
108
  adb("shell", "am", "force-stop", PAQUETE);
107
109