From 12b85e6579ff752abd2c2de89e22c7e489b14516 Mon Sep 17 00:00:00 2001 From: Pandipipas <62224708+Pandipipas@users.noreply.github.com> Date: Tue, 24 Feb 2026 00:37:48 +0100 Subject: [PATCH] Translate Spanish text to English across docs and code --- README.md | 58 +++--- docs/architecture.md | 36 ++-- docs/refactor-roadmap.md | 278 ++++++++++++-------------- docs/troubleshooting.md | 32 +-- scripts/doctor.mjs | 18 +- src/main/config/runtime-config.ts | 4 +- src/main/main.ts | 4 +- src/main/nodecg/process-manager.ts | 34 ++-- src/tests/icon-path.test.ts | 6 +- src/tests/navigation-security.test.ts | 12 +- src/tests/process-manager.test.ts | 30 +-- src/tests/runtime-config.test.ts | 28 +-- src/tests/timing.test.ts | 4 +- 13 files changed, 262 insertions(+), 282 deletions(-) diff --git a/README.md b/README.md index de96c39..edabdae 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,20 @@ # scoreko-electron-dev -Wrapper de Electron para empaquetar una instalación de NodeCG que incluya el bundle `scoreko-dev`, inspirado en `opeik/runback-electron` pero actualizado a Electron + TypeScript moderno. +Electron wrapper to package a NodeCG installation that includes the `scoreko-dev` bundle, inspired by `opeik/runback-electron` but updated to modern Electron + TypeScript. -## Requisitos clave +## Key requirements -- Node `>=22` (`.nvmrc` incluido). -- Electron fijado en `39.5.1`. +- Node `>=22` (`.nvmrc` included). +- Electron pinned to `39.5.1`. -## Qué hace +## What it does -- Arranca `lib/nodecg/index.js` como proceso hijo desde Electron. -- Muestra la ruta de dashboard de carga del bundle (`/bundles//dashboard/loading.html`) servida por NodeCG mientras inicia. -- Carga el dashboard del bundle en `http://localhost:/bundles//`. -- Empaqueta NodeCG + assets dentro de la app final con `electron-builder`. +- Starts `lib/nodecg/index.js` as a child process from Electron. +- Shows the bundle loading dashboard route (`/bundles//dashboard/loading.html`) served by NodeCG while it starts. +- Loads the bundle dashboard at `http://localhost:/bundles//`. +- Packages NodeCG + assets inside the final app with `electron-builder`. -## Estructura esperada +## Expected structure ```text scoreko-electron-dev/ @@ -31,34 +31,34 @@ scoreko-electron-dev/ ## Scripts -- `npm run dev`: modo desarrollo. -- `npm run build`: compila TypeScript y copia assets. -- `npm run start`: build y ejecución local. -- `npm run test`: build + tests unitarios (`node:test`). -- `npm run doctor`: preflight de configuración y entorno (`lib/nodecg`, permisos, puerto y env vars). -- `npm run lint`: reglas mínimas de calidad con ESLint. -- `npm run format`: validación de formato con Prettier. -- `npm run pack`: genera app sin instalador. -- `npm run dist`: genera instalador. +- `npm run dev`: development mode. +- `npm run build`: compile TypeScript and copy assets. +- `npm run start`: build and local run. +- `npm run test`: build + unit tests (`node:test`). +- `npm run doctor`: configuration/environment preflight (`lib/nodecg`, permissions, port, and env vars). +- `npm run lint`: minimal quality rules with ESLint. +- `npm run format`: format validation with Prettier. +- `npm run pack`: generate app without installer. +- `npm run dist`: generate installer. -## Variables de entorno +## Environment variables -La **fuente única de defaults** está en `.env.example`. +The **single source of truth for defaults** is in `.env.example`. -1. Copia `.env.example` a `.env` (o exporta variables en tu shell/CI). -2. Ajusta sólo lo necesario. -3. Ejecuta `npm run doctor` para validar configuración antes de arrancar. +1. Copy `.env.example` to `.env` (or export variables in your shell/CI). +2. Adjust only what you need. +3. Run `npm run doctor` to validate configuration before starting. -## Build multi-plataforma (iconos) +## Cross-platform build (icons) - `build.win.icon`: `static/icons/icon.ico` - `build.linux.icon`: `static/icons` - `build.mac.icon`: `static/icons/icon.icns` -> El `.icns` se referencia en la configuración de build y debe existir localmente para empaquetar macOS. +> The `.icns` is referenced in build config and must exist locally to package macOS. -## Troubleshooting y arquitectura +## Troubleshooting and architecture -- Guía de troubleshooting: `docs/troubleshooting.md` -- Mapa de arquitectura: `docs/architecture.md` +- Troubleshooting guide: `docs/troubleshooting.md` +- Architecture map: `docs/architecture.md` - Roadmap: `docs/refactor-roadmap.md` diff --git a/docs/architecture.md b/docs/architecture.md index b716afd..94c2afc 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -1,24 +1,24 @@ -# Arquitectura del proceso principal +# Main process architecture -## Flujo de arranque +## Startup flow -1. `src/main/main.ts` carga `appConfig` desde `config/runtime-config.ts`. -2. Crea ventanas (`windows/window-factory.ts`). -3. Arranca NodeCG con `nodecg/process-manager.ts`. -4. Espera readiness HTTP y muestra loading -> dashboard principal. -5. En cierre, ejecuta un único flujo de stop graceful para evitar procesos huérfanos. +1. `src/main/main.ts` loads `appConfig` from `config/runtime-config.ts`. +2. Creates windows (`windows/window-factory.ts`). +3. Starts NodeCG with `nodecg/process-manager.ts`. +4. Waits for HTTP readiness and shows loading -> main dashboard. +5. On shutdown, runs a single graceful-stop flow to avoid orphan processes. -## Módulos principales +## Main modules -- `config/runtime-config.ts`: lectura/validación de env vars. -- `nodecg/process-manager.ts`: start, readiness y stop de NodeCG, validaciones de instalación/permisos/puerto. -- `windows/window-factory.ts`: creación de ventanas y política de navegación. -- `windows/navigation-security.ts`: allowlist de navegación interna y esquemas externos seguros. -- `errors/error-presenter.ts`: presentación de errores fatales. -- `errors/logger.ts`: logging estructurado (`info/warn/error/debug`). +- `config/runtime-config.ts`: read/validate env vars. +- `nodecg/process-manager.ts`: start, readiness, and stop for NodeCG; install/permission/port validation. +- `windows/window-factory.ts`: window creation and navigation policy. +- `windows/navigation-security.ts`: internal navigation allowlist and safe external schemes. +- `errors/error-presenter.ts`: fatal error presentation. +- `errors/logger.ts`: structured logging (`info/warn/error/debug`). -## Principios +## Principles -- Refactors mecánicos primero. -- Hardening incremental con fallback conservador. -- Validación automática por `typecheck`, `build`, `test`, `doctor`, `lint`. +- Mechanical refactors first. +- Incremental hardening with conservative fallback. +- Automated validation via `typecheck`, `build`, `test`, `doctor`, `lint`. diff --git a/docs/refactor-roadmap.md b/docs/refactor-roadmap.md index c15445a..621c706 100644 --- a/docs/refactor-roadmap.md +++ b/docs/refactor-roadmap.md @@ -1,215 +1,195 @@ -# Roadmap de refactor, limpieza y mejoras (sin romper funcionalidad) +# Refactor, cleanup, and improvement roadmap (without breaking functionality) -Este documento detalla una propuesta de mejoras para `scoreko-electron-dev` priorizando **cero regresiones**. Cada iniciativa incluye objetivo, acciones concretas y estrategia de implementación segura. +This document outlines an improvement proposal for `scoreko-electron-dev`, prioritizing **zero regressions**. Each initiative includes a goal, concrete actions, and a safe implementation strategy. -## 1) Arquitectura y organización del código +## 1) Architecture and code organization -### 1.1 Separar `main.ts` por responsabilidades +### 1.1 Split `main.ts` responsibilities -- **Problema actual:** `src/main/main.ts` concentra configuración, UI, lifecycle, gestión de procesos, manejo de errores y utilidades. -- **Acciones:** - - Crear `src/main/config/runtime-config.ts` para parseo de env vars. - - Crear `src/main/nodecg/process-manager.ts` para `start/stop/wait-ready`. - - Crear `src/main/windows/window-factory.ts` para creación de ventanas. - - Crear `src/main/errors/error-presenter.ts` para logging + `dialog.showErrorBox`. -- **Riesgo:** bajo, si se conserva API interna por pasos. -- **Implementación segura:** mover funciones sin cambiar lógica, cubrir con tests unitarios antes de modificar comportamiento. +- **Current issue:** `src/main/main.ts` mixes configuration, UI, lifecycle, process management, error handling, and utilities. +- **Actions:** + - Create `src/main/config/runtime-config.ts` for env var parsing. + - Create `src/main/nodecg/process-manager.ts` for `start/stop/wait-ready`. + - Create `src/main/windows/window-factory.ts` for window creation. + - Create `src/main/errors/error-presenter.ts` for logging + `dialog.showErrorBox`. +- **Safe implementation:** move functions without changing behavior, cover with unit tests before modifying logic. -### 1.2 Consolidar constantes de runtime +### 1.2 Consolidate runtime constants -- **Problema actual:** constantes de URLs y tamaños están dispersas. -- **Acciones:** agrupar en `src/main/constants.ts` y tiparlas con `as const`. -- **Beneficio:** facilita ajuste de defaults y revisiones. +- **Current issue:** URL and size constants are scattered. +- **Action:** group them in `src/main/constants.ts` and type with `as const`. +- **Benefit:** easier default tuning and review. -### 1.3 Normalizar naming interno +### 1.3 Improve naming -- **Mejoras propuestas:** - - `runtimeConfig` → `appConfig`. - - `nodecgPath` → `nodecgRootPath`. - - `RUNTIME_NAME` → `NODE_RUNTIME_NAME`. -- **Objetivo:** nombres más semánticos para mantenimiento. +- **Goal:** more semantic names for maintainability. -## 2) Robustez de proceso NodeCG +## 2) NodeCG process robustness -### 2.1 Endurecer validaciones de instalación +### 2.1 Harden install validations -- **Mejoras:** - - Validar permisos de lectura/escritura en `lib/nodecg`. - - Validar si el puerto está ocupado antes de lanzar. - - Incluir diagnóstico más accionable en mensajes de error (comando sugerido exacto). -- **Implementación segura:** agregar validaciones opt-in primero con logs, luego convertir a hard-fail. +- **Actions:** + - Validate read/write permissions in `lib/nodecg`. + - Validate whether the port is occupied before launching. + - Include more actionable diagnostics in error messages (exact suggested command). +- **Safe implementation:** add opt-in validations with logs first, then convert to hard-fail. -### 2.2 Mejorar el check de “ready” +### 2.2 Improve the "ready" check -- **Problema:** readiness actual con `GET /` + `response.ok || 404` puede dar falsos positivos. -- **Acciones:** - - Intentar endpoint más explícito de NodeCG si existe. - - Si no existe, añadir secuencia de checks con retries exponenciales + jitter. -- **Compatibilidad:** mantener fallback al check actual inicialmente. +- **Current issue:** readiness with `GET /` + `response.ok || 404` can give false positives. +- **Actions:** + - Try a more explicit NodeCG endpoint if available. + - Otherwise add a check sequence with exponential retries + jitter. -### 2.3 Control del shutdown más determinista +### 2.3 More deterministic shutdown control -- **Acciones:** - - Añadir estado explícito (`starting/running/stopping/stopped`). - - Evitar dobles señales en hooks `before-quit`, `will-quit`, `process.exit`. - - Registrar duración de parada para diagnósticos. +- **Actions:** + - Add explicit state (`starting/running/stopping/stopped`). + - Avoid duplicate signals in `before-quit`, `will-quit`, and `process.exit` hooks. + - Record stop duration for diagnostics. -## 3) Ventanas Electron y UX de carga +## 3) Electron windows and loading UX -### 3.1 Rework de loading flow +### 3.1 Rework loading flow -- **Mejoras:** - - Evitar cargar la ventana principal demasiado pronto si falla dashboard. - - Añadir timeout específico para `mainWindow.loadURL`. - - Incluir fallback de pantalla de error amigable dentro de Electron. +- **Actions:** + - Avoid loading the main window too early if the dashboard fails. + - Add a specific timeout for `mainWindow.loadURL`. + - Include a friendly error-screen fallback inside Electron. -### 3.2 Seguridad de navegación +### 3.2 Navigation security -- **Acciones:** - - Validar que `setWindowOpenHandler` y `will-navigate` permitan solo dominio esperado (`localhost:PORT`). - - Rechazar esquemas inseguros (`file:`, `javascript:`). -- **Beneficio:** hardening del proceso principal. +- **Actions:** + - Validate that `setWindowOpenHandler` and `will-navigate` only allow the expected domain (`localhost:PORT`). + - Keep external links in the default browser. -### 3.3 Ajustes de resolución +### 3.3 Resolution settings -- **Mejora:** no fijar `minWidth/minHeight` a 1920x1080 en todos los escenarios. -- **Propuesta segura:** usar valores por env var con defaults actuales para mantener compatibilidad. +- **Improvement:** do not fix `minWidth/minHeight` at 1920x1080 for all scenarios. +- **Safe proposal:** use env-var values with current defaults for compatibility. -## 4) Configuración y variables de entorno +## 4) Configuration and environment variables -### 4.1 Validación tipada de env vars +### 4.1 Typed env var validation -- **Acciones:** - - Introducir esquema (`zod` o validación manual centralizada). - - Rechazar puertos fuera de rango. - - Marcar strings vacíos inválidos en rutas críticas. +- **Actions:** + - Introduce a schema (`zod` or centralized manual validation). + - Reject out-of-range ports. + - Treat empty strings as invalid in critical paths. -### 4.2 Documentación ejecutable +### 4.2 Executable documentation -- **Acciones:** - - Añadir `.env.example` con todos los defaults. - - Script de validación `npm run doctor` para detectar configuración inválida. +- **Actions:** + - Add `.env.example` with all defaults. + - Add `npm run doctor` validation script to detect invalid configuration. -### 4.3 Unificar defaults entre código y README +### 4.3 Unify defaults between code and README -- **Problema:** existe posible drift entre doc y runtime. -- **Acción:** generar bloque de README automáticamente desde una fuente única de config. +- **Current issue:** docs and runtime can drift. +- **Action:** generate the README block automatically from a single config source. -## 5) Build, packaging y distribución +## 5) Build, packaging, and distribution -### 5.1 Revisar iconos por plataforma +### 5.1 Platform icon hardening -- **Problema:** en `build.mac.icon` se usa `.ico` (no ideal para macOS). -- **Acciones:** - - Usar `.icns` en macOS. - - Mantener `.ico` en Windows y set icon set correcto para Linux. -- **Estrategia segura:** fallback conservando lo actual hasta tener assets definitivos. +- **Current issue:** `build.mac.icon` uses `.ico` (not ideal for macOS). +- **Actions:** + - Use `.icns` for macOS. + - Keep `.ico` on Windows and set the correct icon set for Linux. -### 5.2 Pipeline reproducible +### 5.2 Reproducible pipeline -- **Acciones:** - - Asegurar lockfile limpio y versión de Node fijada con `.nvmrc`. - - Añadir CI mínima (`typecheck`, build, smoke test). +- **Actions:** + - Ensure a clean lockfile and pinned Node version with `.nvmrc`. + - Add minimal CI (`typecheck`, `build`, smoke test). -### 5.3 Reducción de tamaño de artefacto +### 5.3 Artifact size reduction -- **Acciones:** - - Revisar qué se copia en `extraResources`. - - Excluir archivos no necesarios (logs, tests, cachés) en empaquetado. +- **Actions:** + - Review what is copied in `extraResources`. + - Exclude unnecessary files (logs, tests, caches) in packaging. -## 6) Calidad de código y testing +## 6) Code quality and testing -### 6.1 Añadir linting/formatting +### 6.1 Add linting/formatting -- **Acciones:** - - Configurar ESLint + Prettier. - - Reglas mínimas: imports ordenados, no variables no usadas, complejidad controlada. +- **Actions:** + - ESLint + Prettier in scripts and CI. + - Minimum rules: sorted imports, no unused variables, controlled complexity. -### 6.2 Unit tests para utilidades críticas +### 6.2 Unit tests for critical utilities -- **Cobertura objetivo inicial:** - - `parseEnvInt`, `getEnv`, `getOptionalEnv`. - - Resolución de icon path. - - Cálculo de delays y timeouts. +- **Coverage target:** + - Env var parsing. + - Navigation allowlist. + - Icon path resolution. + - Delay and timeout calculation. -### 6.3 Integration smoke tests +### 6.3 Main bootstrap smoke tests -- **Acción:** test que verifique arranque controlado de Electron main con mocks (sin UI real). -- **Objetivo:** detectar regresiones de lifecycle y cierre de NodeCG. +- **Action:** test controlled Electron main startup with mocks (no real UI). -## 7) Observabilidad y diagnóstico +## 7) Observability and diagnostics -### 7.1 Logger estructurado +### 7.1 Structured logging -- **Acción:** reemplazar `console.log` por logger con niveles (`info/warn/error/debug`) y contexto. -- **Beneficio:** depuración más rápida en producción. +- **Action:** replace `console.log` with leveled logger (`info/warn/error/debug`) and context. +- **Benefit:** faster production debugging. -### 7.2 Error codes y troubleshooting +### 7.2 Error taxonomy -- **Acciones:** - - Estandarizar errores con códigos (`E_NODECG_NOT_FOUND`, etc.). - - Añadir sección “Troubleshooting” en README con causas/soluciones. +- **Actions:** + - Standardize errors with codes (`E_NODECG_NOT_FOUND`, etc.). + - Add a README troubleshooting section with causes/solutions. -## 8) Limpieza técnica (deuda) +## 8) Technical cleanup (debt) -### 8.1 Eliminar lógica duplicada de cierre +### 8.1 Remove duplicated shutdown logic -- **Problema:** varios handlers llaman `stopNodeCG()`. -- **Acción:** centralizar estrategia de parada en un solo coordinador. +- **Action:** centralize stop strategy in a single coordinator. -### 8.2 Extraer utilidades de proceso SO +### 8.2 Platform-specific process utils -- **Acción:** separar lógica Windows (`taskkill`) y POSIX (`process.kill`) en módulos específicos. +- **Action:** split Windows (`taskkill`) and POSIX (`process.kill`) logic into dedicated modules. -### 8.3 Revisar `shell: true` en spawn +## 9) Suggested renames -- **Motivo:** reducir superficie y comportamiento inesperado. -- **Plan seguro:** introducir feature-flag para comparar comportamiento antes de retirar. +- `waitForNodeCGReady` -> `waitForNodeCGHttpReady` +- `stopNodeCG` -> `stopNodeCGProcess` +- `createLoadingWindow` -> `createSplashWindow` -## 9) Renombrados sugeridos (bajo riesgo) +> Apply renames in atomic changes with tests to avoid breakages. -- `loadingRoute` → `loadingDashboardRoute`. -- `dashboardRoute` → `mainDashboardRoute`. -- `baseUrl` → `nodecgBaseUrl`. -- `launch()` → `launchApplication()`. -- `stopNodeCG()` → `stopNodecgProcessGracefully()`. +## 10) New files to create -> Aplicar renombrados con cambios atómicos y tests para evitar breakages. +- `docs/architecture.md` (module map and startup flow). +- `docs/troubleshooting.md` (common failures + commands). +- `src/main/errors/logger.ts`. +- `src/main/main-controller.ts` (orchestrator for startup/shutdown). -## 10) Qué crearía nuevo +## 11) What to remove (if unused) -- `docs/architecture.md` (mapa de módulos y flujo de inicio). -- `docs/troubleshooting.md` (errores comunes). -- `.env.example` (variables documentadas y defaults). -- `scripts/doctor.mjs` (chequeo preflight). -- `tests/main/*.test.ts` (suite base de utilidades y lifecycle). +- Legacy comments that describe behavior no longer present. +- Duplicate icon-path legacy references if there is already one strategy. -## 11) Qué eliminaría (si no se usa) +## 12) Phase-based execution plan (without breaking anything) -- Configuraciones o scripts redundantes de rebuild nativo cuando no sean necesarios para el bundle actual. -- Referencias legacy de rutas de iconos duplicadas si ya hay estrategia única. +1. **Phase 0 – Safety baseline:** add tests for current behavior. +2. **Phase 1 – Mechanical refactor:** move code to modules without changing behavior. +3. **Phase 2 – Observability:** structured logs and clear errors. +4. **Phase 3 – Hardening:** config validation, navigation security, and shutdown. +5. **Phase 4 – Build/CI:** icon fixes, pipeline hardening, and smoke tests. +6. **Phase 5 – Final cleanup:** renames and residual debt removal. -> Eliminar solo tras comprobar uso real en CI y build local. +## 13) Per-change acceptance criteria -## 12) Plan de ejecución por fases (sin romper nada) +- App still starts in dev and packaged mode. +- Main dashboard loads after NodeCG readiness. +- Controlled shutdown with no orphan NodeCG processes. +- Unit and smoke tests pass. -1. **Fase 0 – Baseline:** typecheck + build + smoke manual. -2. **Fase 1 – Refactor mecánico:** mover código a módulos sin cambiar comportamiento. -3. **Fase 2 – Tests:** cubrir utilidades y lifecycle. -4. **Fase 3 – Hardening:** validación de config, seguridad de navegación y shutdown. -5. **Fase 4 – Packaging:** iconos por plataforma y CI. -6. **Fase 5 – Limpieza final:** renombrados y eliminación de deuda residual. +## 14) Recommended prioritization -## 13) Criterios de aceptación por cambio - -- `npm run typecheck` en verde. -- `npm run build` en verde. -- Arranque correcto mostrando loading y luego dashboard. -- Cierre correcto sin procesos huérfanos de NodeCG. -- Sin cambios visibles no intencionales en UX. - -## 14) Priorización recomendada - -- **Alta:** separación de módulos, tests base, hardening de shutdown y readiness. -- **Media:** validación tipada de env vars, logging estructurado, CI. -- **Baja:** renombrados cosméticos y optimización de tamaño de paquete. +- **High:** process robustness, config validation, and deterministic shutdown. +- **Medium:** observability, docs, and build hardening. +- **Low:** cosmetic renames and package-size optimization. diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 866587b..310df27 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -1,27 +1,27 @@ # Troubleshooting -## `No existe la carpeta NodeCG` +## `NodeCG folder does not exist` -- Verifica que exista `lib/nodecg`. -- Asegúrate de que el proyecto contiene una instalación completa de NodeCG. +- Verify `lib/nodecg` exists. +- Make sure the project contains a full NodeCG installation. -## `Sin permisos de lectura/escritura sobre NodeCG` +## `No read/write permissions on NodeCG` -- Ajusta permisos de `lib/nodecg` para el usuario que ejecuta Electron. -- En Linux/macOS: `chmod -R u+rw lib/nodecg` (según tu política local). +- Adjust permissions on `lib/nodecg` for the user running Electron. +- On Linux/macOS: `chmod -R u+rw lib/nodecg` (according to your local policy). -## `El puerto ya está en uso` +## `Port is already in use` -- Libera el puerto o define `NODECG_PORT` en `.env`. -- Usa `npm run doctor` para validar disponibilidad antes de arrancar. +- Free the port or set `NODECG_PORT` in `.env`. +- Use `npm run doctor` to validate availability before startup. -## `Timeout esperando NodeCG` +## `Timeout while waiting for NodeCG` -- Revisa logs de NodeCG en la salida estándar. -- Incrementa `NODECG_STARTUP_TIMEOUT_MS` si el entorno es lento. -- Verifica dependencias de NodeCG (`cd lib/nodecg && npm install`). +- Check NodeCG logs in standard output. +- Increase `NODECG_STARTUP_TIMEOUT_MS` if the environment is slow. +- Verify NodeCG dependencies (`cd lib/nodecg && npm install`). -## Build macOS falla por icono +## macOS build fails because of icon -- La configuración espera `static/icons/icon.icns`. -- Crea ese archivo antes de ejecutar empaquetado para macOS. +- The configuration expects `static/icons/icon.icns`. +- Create that file before running macOS packaging. diff --git a/scripts/doctor.mjs b/scripts/doctor.mjs index 7cf3ed0..f44a330 100644 --- a/scripts/doctor.mjs +++ b/scripts/doctor.mjs @@ -16,11 +16,11 @@ function parsePort(name, fallback) { const raw = process.env[name] ?? fallback; const parsed = Number.parseInt(raw, 10); if (!Number.isFinite(parsed) || parsed < 1 || parsed > 65535) { - addCheck(false, `${name} inválido`, `Debe ser un entero entre 1 y 65535. Valor recibido: '${raw}'.`); + addCheck(false, `${name} invalid`, `It must be an integer between 1 and 65535. Received value: '${raw}'.`); return null; } - addCheck(true, `${name} válido`, `${parsed}`); + addCheck(true, `${name} valid`, `${parsed}`); return parsed; } @@ -28,11 +28,11 @@ function parseIntInRange(name, fallback, min, max) { const raw = process.env[name] ?? String(fallback); const parsed = Number.parseInt(raw, 10); if (!Number.isFinite(parsed) || parsed < min || parsed > max) { - addCheck(false, `${name} inválido`, `Debe ser un entero entre ${min} y ${max}. Valor recibido: '${raw}'.`); + addCheck(false, `${name} invalid`, `It must be an integer between ${min} and ${max}. Received value: '${raw}'.`); return; } - addCheck(true, `${name} válido`, `${parsed}`); + addCheck(true, `${name} valid`, `${parsed}`); } function checkNodecgInstall() { @@ -46,9 +46,9 @@ function checkNodecgInstall() { try { fs.accessSync(nodecgRootPath, fs.constants.R_OK | fs.constants.W_OK); - addCheck(true, "Permisos lib/nodecg", "Lectura/escritura OK"); + addCheck(true, "lib/nodecg permissions", "Read/write OK"); } catch { - addCheck(false, "Permisos lib/nodecg", "Sin permisos de lectura/escritura en lib/nodecg"); + addCheck(false, "lib/nodecg permissions", "No read/write permissions in lib/nodecg"); } } @@ -57,13 +57,13 @@ function checkPortAvailability(port) { const server = net.createServer(); server.once("error", () => { - addCheck(false, `Puerto ${port}`, "Está ocupado. Libéralo o cambia NODECG_PORT."); + addCheck(false, `Port ${port}`, "It is in use. Free it or change NODECG_PORT."); resolve(); }); server.listen(port, "127.0.0.1", () => { server.close(() => { - addCheck(true, `Puerto ${port}`, "Disponible"); + addCheck(true, `Port ${port}`, "Available"); resolve(); }); }); @@ -92,7 +92,7 @@ async function main() { return; } - console.log("\nDoctor finalizado: configuración válida."); + console.log("\nDoctor finished: valid configuration."); } main(); diff --git a/src/main/config/runtime-config.ts b/src/main/config/runtime-config.ts index b9f09d4..6074e20 100644 --- a/src/main/config/runtime-config.ts +++ b/src/main/config/runtime-config.ts @@ -56,7 +56,7 @@ export function parseEnvIntInRange(name: string, fallback: number, min: number, const parsedValue = Number.parseInt(rawValue, 10); if (!Number.isFinite(parsedValue) || parsedValue < min || parsedValue > max) { - throw new Error(`La variable ${name} debe ser un entero entre ${min} y ${max}. Valor recibido: '${rawValue}'.`); + throw new Error(`The ${name} variable must be an integer between ${min} and ${max}. Received value: '${rawValue}'.`); } return parsedValue; @@ -68,7 +68,7 @@ export function parseEnvPort(name: string, fallback: string): string { if (!Number.isFinite(parsedValue) || parsedValue < MIN_TCP_PORT || parsedValue > MAX_TCP_PORT) { throw new Error( - `La variable ${name} debe ser un puerto TCP válido (${MIN_TCP_PORT}-${MAX_TCP_PORT}). Valor recibido: '${rawValue}'.`, + `The ${name} variable must be a valid TCP port (${MIN_TCP_PORT}-${MAX_TCP_PORT}). Received value: '${rawValue}'.`, ); } diff --git a/src/main/main.ts b/src/main/main.ts index b424ca8..a14bf8d 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -146,9 +146,9 @@ process.on("exit", () => { }); process.on("uncaughtException", (error) => { - showFatalError("Error inesperado en el proceso principal de Electron.", error); + showFatalError("Unexpected error in Electron main process.", error); }); process.on("unhandledRejection", (reason) => { - showFatalError("Promesa no controlada en el proceso principal de Electron.", reason); + showFatalError("Unhandled promise in Electron main process.", reason); }); diff --git a/src/main/nodecg/process-manager.ts b/src/main/nodecg/process-manager.ts index 6c9db98..b8064ea 100644 --- a/src/main/nodecg/process-manager.ts +++ b/src/main/nodecg/process-manager.ts @@ -64,7 +64,7 @@ export function createNodecgProcessManager({ const isPortAvailable = await resolvedDeps.probePortAvailable(portAsNumber); if (!isPortAvailable) { throw new Error( - `El puerto ${appConfig.nodecgPort} ya está en uso. Cierra el proceso que lo ocupa o configura NODECG_PORT antes de iniciar.`, + `Port ${appConfig.nodecgPort} is already in use. Stop the process using it or set NODECG_PORT before starting.`, ); } @@ -110,16 +110,16 @@ export function createNodecgProcessManager({ while (Date.now() - startTime < appConfig.startupTimeoutMs) { if (!nodecgProcess) { const exitDetails = lastExit - ? `Última salida registrada: code=${lastExit.code ?? "null"}, signal=${lastExit.signal ?? "none"}.` - : "No se registró código de salida del proceso NodeCG."; - const stderrDetails = lastStderrLine ? `Último stderr: ${lastStderrLine}` : "Sin salida stderr capturada."; + ? `Last recorded exit: code=${lastExit.code ?? "null"}, signal=${lastExit.signal ?? "none"}.` + : "No NodeCG process exit code was recorded."; + const stderrDetails = lastStderrLine ? `Last stderr: ${lastStderrLine}` : "No stderr output captured."; throw new Error( [ - "NodeCG terminó antes de estar listo.", + "NodeCG exited before becoming ready.", exitDetails, stderrDetails, - `Ruta NodeCG: ${nodecgRootPath}`, - "Revisa que lib/nodecg tenga dependencias instaladas y que el bundle exista.", + `NodeCG path: ${nodecgRootPath}`, + "Check that lib/nodecg dependencies are installed and the bundle exists.", ].join("\n"), ); } @@ -136,7 +136,7 @@ export function createNodecgProcessManager({ await sleep(500, resolvedDeps.setTimer); } - throw new Error(`Timeout esperando NodeCG en ${nodecgBaseUrl} (${appConfig.startupTimeoutMs}ms).`); + throw new Error(`Timeout waiting for NodeCG at ${nodecgBaseUrl} (${appConfig.startupTimeoutMs}ms).`); }; const stopNodecgProcessGracefully = (): Promise => { @@ -223,23 +223,23 @@ function validateNodecgInstall( const bundlePath = path.join(nodecgRootPath, "bundles", bundleName); if (!pathExists(nodecgRootPath)) { - throw new Error(`No existe la carpeta NodeCG: ${nodecgRootPath}`); + throw new Error(`NodeCG folder does not exist: ${nodecgRootPath}`); } if (!hasReadWriteAccessToPath(nodecgRootPath)) { - throw new Error(`Sin permisos de lectura/escritura sobre NodeCG: ${nodecgRootPath}`); + throw new Error(`No read/write permissions on NodeCG: ${nodecgRootPath}`); } if (!pathExists(indexPath)) { - throw new Error(`No se encontró ${indexPath}. Copia una instalación completa de NodeCG en lib/nodecg.`); + throw new Error(`${indexPath} was not found. Copy a full NodeCG installation into lib/nodecg.`); } if (!pathExists(nodecgBootstrapPath)) { throw new Error( [ - "NodeCG está presente pero faltan dependencias internas.", - `No existe: ${nodecgBootstrapPath}`, - "Solución: entra a lib/nodecg e instala dependencias:", + "NodeCG is present but internal dependencies are missing.", + `Not found: ${nodecgBootstrapPath}`, + "Solution: enter lib/nodecg and install dependencies:", " npm install", ].join("\n"), ); @@ -248,9 +248,9 @@ function validateNodecgInstall( if (!pathExists(bundlePath)) { throw new Error( [ - `No se encontró el bundle '${bundleName}'.`, - `Ruta esperada: ${bundlePath}`, - "Copia/clona tu bundle dentro de lib/nodecg/bundles antes de ejecutar Electron.", + `Bundle '${bundleName}' was not found.`, + `Expected path: ${bundlePath}`, + "Copy/clone your bundle inside lib/nodecg/bundles before running Electron.", ].join("\n"), ); } diff --git a/src/tests/icon-path.test.ts b/src/tests/icon-path.test.ts index 084be08..a5c909d 100644 --- a/src/tests/icon-path.test.ts +++ b/src/tests/icon-path.test.ts @@ -19,7 +19,7 @@ function getBaseConfig(): AppRuntimeConfig { }; } -test("resolveAppIconPath prioriza iconPathOverride cuando existe", () => { +test("resolveAppIconPath prioritizes iconPathOverride when present", () => { const appConfig: AppRuntimeConfig = { ...getBaseConfig(), iconPathOverride: "/custom/icon.ico", @@ -30,7 +30,7 @@ test("resolveAppIconPath prioriza iconPathOverride cuando existe", () => { assert.equal(iconPath, "/custom/icon.ico"); }); -test("resolveAppIconPath cae al primer icono por defecto existente", () => { +test("resolveAppIconPath falls back to the first existing default icon", () => { const appConfig = getBaseConfig(); const expectedIconPath = path.join("/app", "static", "icons", "icon.png"); @@ -39,7 +39,7 @@ test("resolveAppIconPath cae al primer icono por defecto existente", () => { assert.equal(iconPath, expectedIconPath); }); -test("resolveAppIconPath devuelve undefined cuando no hay iconos", () => { +test("resolveAppIconPath returns undefined when no icons exist", () => { const appConfig = getBaseConfig(); const iconPath = resolveAppIconPath(appConfig, "/app", () => false); diff --git a/src/tests/navigation-security.test.ts b/src/tests/navigation-security.test.ts index 229ca40..b13b3e5 100644 --- a/src/tests/navigation-security.test.ts +++ b/src/tests/navigation-security.test.ts @@ -5,37 +5,37 @@ import { shouldAllowInternalNavigation, shouldOpenExternalNavigation } from "../ const dashboardUrl = "http://localhost:9090/bundles/scoreko-dev/dashboard/main.html"; -test("shouldAllowInternalNavigation permite navegación interna esperada", () => { +test("shouldAllowInternalNavigation allows expected internal navigation", () => { assert.equal( shouldAllowInternalNavigation("http://127.0.0.1:9090/bundles/scoreko-dev/dashboard/page.html", dashboardUrl), true, ); }); -test("shouldAllowInternalNavigation rechaza host no permitido", () => { +test("shouldAllowInternalNavigation rejects disallowed host", () => { assert.equal( shouldAllowInternalNavigation("http://evil.local:9090/bundles/scoreko-dev/dashboard/page.html", dashboardUrl), false, ); }); -test("shouldAllowInternalNavigation rechaza puerto distinto", () => { +test("shouldAllowInternalNavigation rejects different port", () => { assert.equal( shouldAllowInternalNavigation("http://localhost:8080/bundles/scoreko-dev/dashboard/page.html", dashboardUrl), false, ); }); -test("shouldAllowInternalNavigation rechaza esquemas inseguros", () => { +test("shouldAllowInternalNavigation rejects unsafe schemes", () => { assert.equal(shouldAllowInternalNavigation("javascript:alert(1)", dashboardUrl), false); }); -test("shouldOpenExternalNavigation permite protocolos externos seguros", () => { +test("shouldOpenExternalNavigation allows safe external protocols", () => { assert.equal(shouldOpenExternalNavigation("https://scoreko.com/docs"), true); assert.equal(shouldOpenExternalNavigation("mailto:test@scoreko.com"), true); }); -test("shouldOpenExternalNavigation rechaza protocolos inseguros", () => { +test("shouldOpenExternalNavigation rejects unsafe protocols", () => { assert.equal(shouldOpenExternalNavigation("file:///etc/passwd"), false); assert.equal(shouldOpenExternalNavigation("javascript:alert(1)"), false); }); diff --git a/src/tests/process-manager.test.ts b/src/tests/process-manager.test.ts index 579b5fb..356b32d 100644 --- a/src/tests/process-manager.test.ts +++ b/src/tests/process-manager.test.ts @@ -31,7 +31,7 @@ function getBaseConfig(): AppRuntimeConfig { }; } -test("startNodeCG valida instalación de NodeCG antes de arrancar", async () => { +test("startNodeCG validates NodeCG installation before starting", async () => { const manager = createNodecgProcessManager({ isDev: true, nodecgRootPath: "/fake/nodecg", @@ -41,17 +41,17 @@ test("startNodeCG valida instalación de NodeCG antes de arrancar", async () => deps: { pathExists: () => false, spawnProcess: () => { - throw new Error("no debe intentar arrancar si la validación falla"); + throw new Error("it must not try to start if validation fails"); }, }, }); await assert.rejects(async () => { await manager.startNodecgProcess(); - }, /No existe la carpeta NodeCG/); + }, /NodeCG folder does not exist/); }); -test("startNodeCG falla si no hay permisos de lectura/escritura", async () => { +test("startNodeCG fails when there are no read/write permissions", async () => { const manager = createNodecgProcessManager({ isDev: true, nodecgRootPath: "/fake/nodecg", @@ -66,10 +66,10 @@ test("startNodeCG falla si no hay permisos de lectura/escritura", async () => { await assert.rejects(async () => { await manager.startNodecgProcess(); - }, /Sin permisos de lectura\/escritura/); + }, /No read\/write permissions on NodeCG/); }); -test("waitForNodeCGReady resuelve cuando el endpoint responde 404", async () => { +test("waitForNodeCGReady resolves when endpoint returns 404", async () => { const child = new MockChildProcess(4321); const manager = createNodecgProcessManager({ isDev: true, @@ -99,7 +99,7 @@ test("waitForNodeCGReady resuelve cuando el endpoint responde 404", async () => }); }); -test("stopNodeCG envía SIGTERM y luego SIGKILL si el proceso no sale", async () => { +test("stopNodeCG sends SIGTERM and then SIGKILL if the process does not exit", async () => { const child = new MockChildProcess(9999); const timers: Array<() => void> = []; const killSignals: Array<{ pid: number; signal: NodeJS.Signals }> = []; @@ -147,7 +147,7 @@ test("stopNodeCG envía SIGTERM y luego SIGKILL si el proceso no sale", async () await stopPromise; }); -test("stopNodeCG reutiliza la misma promesa cuando se invoca en paralelo", async () => { +test("stopNodeCG reuses the same promise when invoked in parallel", async () => { const child = new MockChildProcess(5555); const manager = createNodecgProcessManager({ @@ -179,7 +179,7 @@ test("stopNodeCG reutiliza la misma promesa cuando se invoca en paralelo", async await firstStop; }); -test("stopNodeCG normaliza timeout negativo a cero", async () => { +test("stopNodeCG normalizes negative timeout to zero", async () => { const child = new MockChildProcess(7777); const timeouts: number[] = []; @@ -218,7 +218,7 @@ test("stopNodeCG normaliza timeout negativo a cero", async () => { await stopPromise; }); -test("startNodeCG falla si el puerto ya está ocupado", async () => { +test("startNodeCG fails if the port is already in use", async () => { const manager = createNodecgProcessManager({ isDev: true, nodecgRootPath: "/fake/nodecg", @@ -234,10 +234,10 @@ test("startNodeCG falla si el puerto ya está ocupado", async () => { await assert.rejects(async () => { await manager.startNodecgProcess(); - }, /ya está en uso/); + }, /is already in use/); }); -test("waitForNodeCGReady expone diagnóstico cuando NodeCG sale antes de readiness", async () => { +test("waitForNodeCGReady exposes diagnostics when NodeCG exits before readiness", async () => { const child = new MockChildProcess(4242); const manager = createNodecgProcessManager({ isDev: true, @@ -272,9 +272,9 @@ test("waitForNodeCGReady expone diagnóstico cuando NodeCG sale antes de readine }, (error: unknown) => { assert.ok(error instanceof Error); - assert.match(error.message, /NodeCG terminó antes de estar listo/); - assert.match(error.message, /Última salida registrada/); - assert.match(error.message, /Ruta NodeCG: \/fake\/nodecg/); + assert.match(error.message, /NodeCG exited before becoming ready/); + assert.match(error.message, /Last recorded exit/); + assert.match(error.message, /NodeCG path: \/fake\/nodecg/); return true; }, ); diff --git a/src/tests/runtime-config.test.ts b/src/tests/runtime-config.test.ts index d2653fa..aab385d 100644 --- a/src/tests/runtime-config.test.ts +++ b/src/tests/runtime-config.test.ts @@ -24,61 +24,61 @@ function withEnv(name: string, value: string | undefined, run: () => void): void } } -test("getOptionalEnv devuelve undefined para variable ausente", () => { +test("getOptionalEnv returns undefined for missing variable", () => { withEnv("TEST_OPTIONAL_ENV", undefined, () => { assert.equal(getOptionalEnv("TEST_OPTIONAL_ENV"), undefined); }); }); -test("getOptionalEnv recorta espacios y devuelve valor", () => { +test("getOptionalEnv trims spaces and returns value", () => { withEnv("TEST_OPTIONAL_ENV", " scoreko ", () => { assert.equal(getOptionalEnv("TEST_OPTIONAL_ENV"), "scoreko"); }); }); -test("getEnv devuelve fallback para valor vacío", () => { +test("getEnv returns fallback for empty value", () => { withEnv("TEST_ENV", " ", () => { assert.equal(getEnv("TEST_ENV", "fallback"), "fallback"); }); }); -test("getEnv devuelve el valor cuando existe", () => { - withEnv("TEST_ENV", "valor", () => { - assert.equal(getEnv("TEST_ENV", "fallback"), "valor"); +test("getEnv returns the value when present", () => { + withEnv("TEST_ENV", "value", () => { + assert.equal(getEnv("TEST_ENV", "fallback"), "value"); }); }); -test("parseEnvInt devuelve fallback para valores inválidos", () => { +test("parseEnvInt returns fallback for invalid values", () => { withEnv("TEST_ENV_INT", "abc", () => { assert.equal(parseEnvInt("TEST_ENV_INT", 100), 100); }); }); -test("parseEnvInt parsea enteros válidos", () => { +test("parseEnvInt parses valid integers", () => { withEnv("TEST_ENV_INT", "4500", () => { assert.equal(parseEnvInt("TEST_ENV_INT", 100), 4500); }); }); -test("parseEnvIntInRange hace hard-fail para valores fuera de rango", () => { +test("parseEnvIntInRange hard-fails for out-of-range values", () => { withEnv("TEST_ENV_INT_RANGE", "999", () => { - assert.throws(() => parseEnvIntInRange("TEST_ENV_INT_RANGE", 100, 0, 100), /debe ser un entero/); + assert.throws(() => parseEnvIntInRange("TEST_ENV_INT_RANGE", 100, 0, 100), /must be an integer/); }); }); -test("parseEnvIntInRange acepta valor válido", () => { +test("parseEnvIntInRange accepts valid value", () => { withEnv("TEST_ENV_INT_RANGE", "42", () => { assert.equal(parseEnvIntInRange("TEST_ENV_INT_RANGE", 100, 0, 100), 42); }); }); -test("parseEnvPort valida rango TCP", () => { +test("parseEnvPort validates TCP range", () => { withEnv("TEST_ENV_PORT", "70000", () => { - assert.throws(() => parseEnvPort("TEST_ENV_PORT", "9090"), /puerto TCP válido/); + assert.throws(() => parseEnvPort("TEST_ENV_PORT", "9090"), /valid TCP port/); }); }); -test("parseEnvPort normaliza el puerto válido", () => { +test("parseEnvPort normalizes valid port", () => { withEnv("TEST_ENV_PORT", "009090", () => { assert.equal(parseEnvPort("TEST_ENV_PORT", "9090"), "9090"); }); diff --git a/src/tests/timing.test.ts b/src/tests/timing.test.ts index 108e0fe..bc4e3c0 100644 --- a/src/tests/timing.test.ts +++ b/src/tests/timing.test.ts @@ -3,12 +3,12 @@ import test from "node:test"; import { getRemainingDelayMs } from "../main/utils/timing"; -test("getRemainingDelayMs devuelve el tiempo restante cuando aún no se cumple", () => { +test("getRemainingDelayMs returns the remaining time when delay has not elapsed", () => { const remaining = getRemainingDelayMs(10000, 1000, 4000); assert.equal(remaining, 7000); }); -test("getRemainingDelayMs devuelve 0 si ya pasó el delay", () => { +test("getRemainingDelayMs returns 0 when delay has already elapsed", () => { const remaining = getRemainingDelayMs(1000, 1000, 5000); assert.equal(remaining, 0); });