From f3de69decfc18e5445282d4a5257eebd9cbfc828 Mon Sep 17 00:00:00 2001 From: Pandipipas <62224708+Pandipipas@users.noreply.github.com> Date: Tue, 10 Feb 2026 22:45:54 +0100 Subject: [PATCH] refactor startup flow and remove legacy error handling (#14) --- README.md | 38 +--------------- package-lock.json | 6 +-- package.json | 3 -- src/main/main.ts | 114 ++++++++++++++-------------------------------- 4 files changed, 39 insertions(+), 122 deletions(-) diff --git a/README.md b/README.md index ee99ee6..5abfce8 100644 --- a/README.md +++ b/README.md @@ -5,11 +5,6 @@ Wrapper de Electron para empaquetar una instalación de NodeCG que incluya el bu ## Requisitos clave - Electron fijado en `39.5.1`. -- Script requerido para recompilar `better-sqlite3` contra Electron 39.5.1: - -```json -"rebuild:better-sqlite3:electron": "npm --prefix ../.. rebuild better-sqlite3 --runtime=electron --target=39.5.1 --dist-url=https://electronjs.org/headers" -``` ## Qué hace @@ -40,37 +35,8 @@ scoreko-electron-dev/ - `npm run pack`: genera app sin instalador. - `npm run dist`: genera instalador. - `npm run rebuild:native`: rebuild nativo auxiliar en `lib/nodecg`. -- `npm run rebuild:better-sqlite3:electron`: comando exigido para rebuild de `better-sqlite3`. - - -## Troubleshooting - -### Error: `Cannot find module 'bindings'` - -Si aparece en `database-adapter-sqlite-legacy/node_modules/better-sqlite3/lib/database.js`, faltan dependencias del workspace sqlite legacy. - -Ejecuta: - -```bash -cd lib/nodecg/workspaces/database-adapter-sqlite-legacy -npm install -npm install bindings --no-save -cd ../../../../ -npm run rebuild:native -``` - -### Error: `NODE_MODULE_VERSION` - -Recompila nativos contra Electron 39.5.1: - -```bash -npm run rebuild:native -npm run rebuild:better-sqlite3:electron -``` - - ## Variables de entorno útiles - `NODECG_BUNDLE_NAME` (default: `scoreko-dev`) -- `SCOREKO_DASHBOARD_ROUTE` (default: `dashboard/index.html`) -- `SCOREKO_LOADING_ROUTE` (default: `dashboard/loading.html`) +- `SCOREKO_DASHBOARD_ROUTE` (default: `dashboard/example/main.html?standalone=true`) +- `SCOREKO_LOADING_ROUTE` (default: `dashboard/loading/main.html?standalone=true`) diff --git a/package-lock.json b/package-lock.json index 73e3f94..d287303 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,9 +8,6 @@ "name": "scoreko-electron-dev", "version": "0.2.0", "license": "MIT", - "dependencies": { - "source-map-support": "^0.5.21" - }, "devDependencies": { "@electron/rebuild": "^3.7.1", "@types/node": "^22.10.5", @@ -1495,6 +1492,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, "license": "MIT" }, "node_modules/builder-util": { @@ -5092,6 +5090,7 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -5101,6 +5100,7 @@ "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, "license": "MIT", "dependencies": { "buffer-from": "^1.0.0", diff --git a/package.json b/package.json index feae627..3ffa94f 100644 --- a/package.json +++ b/package.json @@ -58,9 +58,6 @@ "engines": { "node": ">=22" }, - "dependencies": { - "source-map-support": "^0.5.21" - }, "devDependencies": { "@electron/rebuild": "^3.7.1", "@types/node": "^22.10.5", diff --git a/src/main/main.ts b/src/main/main.ts index ac349c6..9b6c9cc 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -1,4 +1,4 @@ -import { app, BrowserWindow, dialog, shell } from "electron"; +import { app, BrowserWindow, shell } from "electron"; import { ChildProcess, spawn } from "node:child_process"; import fs from "node:fs"; import path from "node:path"; @@ -8,11 +8,11 @@ const DEFAULT_NODECG_PORT = process.env.NODECG_PORT ?? "9090"; const DEFAULT_BUNDLE_NAME = process.env.NODECG_BUNDLE_NAME ?? "scoreko-dev"; const DEFAULT_DASHBOARD_ROUTE = process.env.SCOREKO_DASHBOARD_ROUTE ?? "dashboard/example/main.html?standalone=true"; const DEFAULT_LOADING_ROUTE = process.env.SCOREKO_LOADING_ROUTE ?? "dashboard/loading/main.html?standalone=true"; -const LOAD_DELAY_MS = Number.parseInt(process.env.ELECTRON_LOAD_DELAY_MS ?? "10000", 10); -const STARTUP_TIMEOUT_MS = Number.parseInt(process.env.NODECG_STARTUP_TIMEOUT_MS ?? "30000", 10); +const LOAD_DELAY_MS = parseEnvInt("ELECTRON_LOAD_DELAY_MS", 10000); +const STARTUP_TIMEOUT_MS = parseEnvInt("NODECG_STARTUP_TIMEOUT_MS", 30000); const USE_SYSTEM_NODE = (process.env.NODECG_USE_SYSTEM_NODE ?? "false").toLowerCase() === "true"; const NODE_BINARY = process.env.NODECG_NODE_BINARY ?? "node"; -const NODECG_KILL_TIMEOUT_MS = Number.parseInt(process.env.NODECG_KILL_TIMEOUT_MS ?? "2500", 10); +const NODECG_KILL_TIMEOUT_MS = parseEnvInt("NODECG_KILL_TIMEOUT_MS", 2500); const isDev = !app.isPackaged; const rootPath = isDev ? path.resolve(__dirname, "../..") : process.resourcesPath; @@ -24,7 +24,6 @@ const baseUrl = `http://127.0.0.1:${DEFAULT_NODECG_PORT}`; let mainWindow: BrowserWindow | null = null; let loadingWindow: BrowserWindow | null = null; let nodecgProcess: ChildProcess | null = null; -let lastNodeCGOutput = ""; let stopNodeCGPromise: Promise | null = null; let isQuitting = false; @@ -47,9 +46,7 @@ function createMainWindow(): BrowserWindow { win.setMenuBarVisibility(false); win.webContents.setWindowOpenHandler(({ url }) => { - shell.openExternal(url).catch((error) => { - log("Error opening external url", url, error); - }); + void shell.openExternal(url); return { action: "deny" }; }); @@ -57,9 +54,7 @@ function createMainWindow(): BrowserWindow { win.webContents.on("will-navigate", (event, url) => { if (url !== dashboardUrl) { event.preventDefault(); - shell.openExternal(url).catch((error) => { - log("Error opening navigation url", url, error); - }); + void shell.openExternal(url); } }); @@ -128,42 +123,6 @@ function validateNodeCGInstall(): void { ].join("\n"), ); } - -} - -function enrichNodeCGFailureMessage(baseMessage: string): string { - if (lastNodeCGOutput.includes("Cannot find module 'bindings'")) { - return [ - baseMessage, - "", - "Detectado error: falta el módulo 'bindings' en el workspace sqlite legacy.", - "Normalmente pasa cuando dependencias del workspace quedaron incompletas.", - "", - "Solución recomendada:", - " 1) cd lib/nodecg/workspaces/database-adapter-sqlite-legacy", - " 2) npm install", - " 3) npm install bindings --no-save", - " 4) cd ../../../../", - " 5) npm run rebuild:native", - ].join("\n"); - } - - if (lastNodeCGOutput.includes("NODE_MODULE_VERSION")) { - return [ - baseMessage, - "", - "Detectado error de módulos nativos compilados para otra versión de Node (NODE_MODULE_VERSION).", - USE_SYSTEM_NODE - ? "Estás en modo Node del sistema: asegúrate de lanzar con Node 22 y recompilar dependencias nativas." - : "Estás en modo standalone (Node interno de Electron). Reinstala/rebuild de dependencias con esta versión de Electron.", - "", - "Solución recomendada:", - " npm run rebuild:native", - " npm run rebuild:better-sqlite3:electron", - ].join("\n"); - } - - return baseMessage; } function startNodeCG(): ChildProcess { @@ -189,13 +148,11 @@ function startNodeCG(): ChildProcess { child.stdout?.on("data", (chunk) => { const text = String(chunk); process.stdout.write(text); - lastNodeCGOutput = `${lastNodeCGOutput}${text}`.slice(-20000); }); child.stderr?.on("data", (chunk) => { const text = String(chunk); process.stderr.write(text); - lastNodeCGOutput = `${lastNodeCGOutput}${text}`.slice(-20000); }); log(`NodeCG started with pid=${child.pid} using ${runtimeName}`); @@ -239,7 +196,6 @@ async function launch(): Promise { mainWindow = createMainWindow(); loadingWindow = createLoadingWindow(); - lastNodeCGOutput = ""; nodecgProcess = startNodeCG(); await waitForNodeCGReady(Date.now()); @@ -248,12 +204,8 @@ async function launch(): Promise { return; } - try { - await loadingWindow.loadURL(loadingUrl); - loadingWindow.show(); - } catch (error) { - log("No se pudo cargar la ruta de loading del bundle", loadingUrl, error); - } + await loadingWindow.loadURL(loadingUrl); + loadingWindow.show(); const loadingShownAt = Date.now(); @@ -261,23 +213,15 @@ async function launch(): Promise { return; } - try { - await mainWindow.loadURL(dashboardUrl); + await mainWindow.loadURL(dashboardUrl); - const remainingLoadingDelay = Math.max(0, LOAD_DELAY_MS - (Date.now() - loadingShownAt)); - if (remainingLoadingDelay > 0) { - await sleep(remainingLoadingDelay); - } - - mainWindow.show(); - } catch (error) { - throw new Error(`No se pudo cargar el dashboard en ${dashboardUrl}. ${String(error)}`); + const remainingLoadingDelay = Math.max(0, LOAD_DELAY_MS - (Date.now() - loadingShownAt)); + if (remainingLoadingDelay > 0) { + await sleep(remainingLoadingDelay); } - if (loadingWindow && !loadingWindow.isDestroyed()) { - loadingWindow.close(); - loadingWindow = null; - } + mainWindow.show(); + closeLoadingWindow(); } function killNodeCGProcessTree(pid: number, signal: NodeJS.Signals): boolean { @@ -356,19 +300,29 @@ function log(...args: unknown[]): void { console.log("[scoreko-electron]", ...args); } +function parseEnvInt(name: string, fallback: number): number { + const rawValue = process.env[name]; + if (!rawValue) { + return fallback; + } + + const parsedValue = Number.parseInt(rawValue, 10); + return Number.isFinite(parsedValue) ? parsedValue : fallback; +} + +function closeLoadingWindow(): void { + if (!loadingWindow || loadingWindow.isDestroyed()) { + return; + } + + loadingWindow.close(); + loadingWindow = null; +} + app.on("ready", () => { launch().catch(async (error: unknown) => { console.error("Failed to launch Scoreko wrapper", error); - - const detail = enrichNodeCGFailureMessage(error instanceof Error ? error.message : String(error)); - - await dialog.showMessageBox({ - type: "error", - title: "No se pudo iniciar Scoreko", - message: "Fallo al iniciar NodeCG", - detail, - }); - + closeLoadingWindow(); app.exit(1); }); });