Merge pull request #3 from Pandipipas/create-electron-wrapper-for-scoreko-dev-xx557u

feat: default NodeCG runtime to system Node (Node 22 friendly)
This commit is contained in:
Pandipipas
2026-02-10 14:15:09 +01:00
committed by GitHub
3 changed files with 48 additions and 13 deletions
+29
View File
@@ -2,6 +2,8 @@
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. 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.
Por defecto, el wrapper arranca NodeCG con **Node del sistema** (`node`), pensado para que uses Node 22 de forma consistente en todo el stack.
## Qué hace ## Qué hace
- Arranca `lib/nodecg/index.js` como proceso hijo desde Electron. - Arranca `lib/nodecg/index.js` como proceso hijo desde Electron.
@@ -56,6 +58,31 @@ npm rebuild better-sqlite3 --update-binary
3. Si persiste, elimina `node_modules` del workspace que falla y vuelve a instalar. 3. Si persiste, elimina `node_modules` del workspace que falla y vuelve a instalar.
## Runtime de NodeCG (Node 22 recomendado)
Por defecto:
- `NODECG_USE_SYSTEM_NODE=true`
- `NODECG_NODE_BINARY=node`
Así NodeCG se ejecuta con tu Node del sistema (recomendado: Node 22).
Ejemplo en PowerShell para forzar Node 22:
```powershell
$env:NODECG_USE_SYSTEM_NODE="true"
$env:NODECG_NODE_BINARY="C:\Program Files\nodejs\node.exe"
node -v # debería mostrar v22.x
npm run dev
```
Si quieres volver al Node interno de Electron:
```powershell
$env:NODECG_USE_SYSTEM_NODE="false"
npm run dev
```
## Variables de entorno opcionales ## Variables de entorno opcionales
Estas variables se leen en `src/main/main.ts`. Estas variables se leen en `src/main/main.ts`.
@@ -65,6 +92,8 @@ Estas variables se leen en `src/main/main.ts`.
- `SCOREKO_DASHBOARD_ROUTE` (default: `dashboard/index.html`) - `SCOREKO_DASHBOARD_ROUTE` (default: `dashboard/index.html`)
- `ELECTRON_LOAD_DELAY_MS` (default: `2500`) - `ELECTRON_LOAD_DELAY_MS` (default: `2500`)
- `NODECG_STARTUP_TIMEOUT_MS` (default: `30000`) - `NODECG_STARTUP_TIMEOUT_MS` (default: `30000`)
- `NODECG_USE_SYSTEM_NODE` (default: `true`)
- `NODECG_NODE_BINARY` (default: `node`)
Ejemplo (PowerShell): Ejemplo (PowerShell):
+1 -1
View File
@@ -54,7 +54,7 @@
} }
}, },
"engines": { "engines": {
"node": ">=20" "node": ">=22"
}, },
"dependencies": { "dependencies": {
"source-map-support": "^0.5.21" "source-map-support": "^0.5.21"
+18 -12
View File
@@ -1,5 +1,5 @@
import { app, BrowserWindow, dialog, shell } from "electron"; import { app, BrowserWindow, dialog, shell } from "electron";
import { ChildProcess, fork } from "node:child_process"; import { ChildProcess, spawn } from "node:child_process";
import fs from "node:fs"; import fs from "node:fs";
import path from "node:path"; import path from "node:path";
@@ -9,6 +9,8 @@ const DEFAULT_BUNDLE_NAME = process.env.NODECG_BUNDLE_NAME ?? "scoreko-dev";
const DEFAULT_DASHBOARD_ROUTE = process.env.SCOREKO_DASHBOARD_ROUTE ?? "dashboard/index.html"; const DEFAULT_DASHBOARD_ROUTE = process.env.SCOREKO_DASHBOARD_ROUTE ?? "dashboard/index.html";
const LOAD_DELAY_MS = Number.parseInt(process.env.ELECTRON_LOAD_DELAY_MS ?? "2500", 10); const LOAD_DELAY_MS = Number.parseInt(process.env.ELECTRON_LOAD_DELAY_MS ?? "2500", 10);
const STARTUP_TIMEOUT_MS = Number.parseInt(process.env.NODECG_STARTUP_TIMEOUT_MS ?? "30000", 10); const STARTUP_TIMEOUT_MS = Number.parseInt(process.env.NODECG_STARTUP_TIMEOUT_MS ?? "30000", 10);
const USE_SYSTEM_NODE = (process.env.NODECG_USE_SYSTEM_NODE ?? "true").toLowerCase() !== "false";
const NODE_BINARY = process.env.NODECG_NODE_BINARY ?? "node";
const isDev = !app.isPackaged; const isDev = !app.isPackaged;
const rootPath = isDev ? path.resolve(__dirname, "../..") : process.resourcesPath; const rootPath = isDev ? path.resolve(__dirname, "../..") : process.resourcesPath;
@@ -130,15 +132,14 @@ function enrichNodeCGFailureMessage(baseMessage: string): string {
return [ return [
baseMessage, baseMessage,
"", "",
"Detectado error de módulos nativos compilados para otra versión de Node/Electron (NODE_MODULE_VERSION).", "Detectado error de módulos nativos compilados para otra versión de Node (NODE_MODULE_VERSION).",
"Esto suele pasar cuando NodeCG/bundles fueron instalados con Node 22 pero Electron corre con Node 20 (o viceversa).", 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 Node de Electron: alinea versiones o cambia NODECG_USE_SYSTEM_NODE=true.",
"", "",
"Solución recomendada (en lib/nodecg):", "Solución recomendada (en lib/nodecg):",
" 1) borrar node_modules y lockfile del workspace que falla (si aplica)", " 1) npm install",
" 2) npm install", " 2) npm rebuild better-sqlite3 --update-binary",
" 3) npm rebuild better-sqlite3 --update-binary",
"",
"También puedes alinear versiones: usar una versión de Electron cuyo Node interno coincida con el usado para compilar addons.",
].join("\n"); ].join("\n");
} }
@@ -148,15 +149,20 @@ function enrichNodeCGFailureMessage(baseMessage: string): string {
function startNodeCG(): ChildProcess { function startNodeCG(): ChildProcess {
validateNodeCGInstall(); validateNodeCGInstall();
const child = fork(path.join(nodecgPath, "index.js"), { const indexPath = path.join(nodecgPath, "index.js");
const runtimeBinary = USE_SYSTEM_NODE ? NODE_BINARY : process.execPath;
const runtimeName = USE_SYSTEM_NODE ? `system node (${NODE_BINARY})` : "electron internal node";
const child = spawn(runtimeBinary, [indexPath], {
cwd: nodecgPath, cwd: nodecgPath,
env: { env: {
...process.env, ...process.env,
ELECTRON_RUN_AS_NODE: "1",
NODE_ENV: isDev ? "development" : "production", NODE_ENV: isDev ? "development" : "production",
NODECG_PORT: DEFAULT_NODECG_PORT, NODECG_PORT: DEFAULT_NODECG_PORT,
...(USE_SYSTEM_NODE ? {} : { ELECTRON_RUN_AS_NODE: "1" }),
}, },
stdio: ["ignore", "pipe", "pipe", "ipc"], stdio: ["ignore", "pipe", "pipe"],
shell: process.platform === "win32",
}); });
child.stdout?.on("data", (chunk) => { child.stdout?.on("data", (chunk) => {
@@ -171,7 +177,7 @@ function startNodeCG(): ChildProcess {
lastNodeCGOutput = `${lastNodeCGOutput}${text}`.slice(-20000); lastNodeCGOutput = `${lastNodeCGOutput}${text}`.slice(-20000);
}); });
log(`NodeCG started with pid=${child.pid}`); log(`NodeCG started with pid=${child.pid} using ${runtimeName}`);
child.on("exit", (code, signal) => { child.on("exit", (code, signal) => {
log(`NodeCG exited code=${code} signal=${signal ?? "none"}`); log(`NodeCG exited code=${code} signal=${signal ?? "none"}`);