mirror of
https://github.com/Pandipipas/scoreko-dev.git
synced 2026-06-06 03:32:06 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d956dfb30b |
@@ -0,0 +1,49 @@
|
|||||||
|
# Electron wrapper (Windows)
|
||||||
|
|
||||||
|
Este wrapper crea una app de escritorio para Windows que lanza NodeCG sin requerir que el usuario final tenga Node.js instalado.
|
||||||
|
|
||||||
|
## Requisitos de build (solo para quien genera el instalador)
|
||||||
|
|
||||||
|
1. Instalar dependencias del bundle raíz:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm install
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Instalar dependencias del wrapper:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd electron
|
||||||
|
pnpm install
|
||||||
|
```
|
||||||
|
|
||||||
|
## Desarrollo local
|
||||||
|
|
||||||
|
Desde `electron/`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm start
|
||||||
|
```
|
||||||
|
|
||||||
|
## Generar instalador `.exe` (comprimido)
|
||||||
|
|
||||||
|
Desde `electron/`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm dist:win
|
||||||
|
```
|
||||||
|
|
||||||
|
Esto genera un instalador NSIS en `electron/dist/` con compresión máxima (`compression: maximum`).
|
||||||
|
|
||||||
|
## Qué incluye el instalador
|
||||||
|
|
||||||
|
- Runtime de Electron (incluye Node embebido).
|
||||||
|
- Dependencia `nodecg` dentro de la app.
|
||||||
|
- El bundle `scoreko-dev` como recurso (`resources/bundle`).
|
||||||
|
|
||||||
|
Con eso, el usuario final instala y ejecuta la app sin instalar Node.js aparte.
|
||||||
|
|
||||||
|
## Variables opcionales
|
||||||
|
|
||||||
|
- `NODECG_PORT` (por defecto `9090`)
|
||||||
|
- `NODECG_HOST` (por defecto `127.0.0.1`)
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
import { app, BrowserWindow } from 'electron';
|
||||||
|
import path from 'node:path';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
import { spawn } from 'node:child_process';
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
|
||||||
|
const BUNDLE_ROOT = app.isPackaged
|
||||||
|
? path.join(process.resourcesPath, 'bundle')
|
||||||
|
: path.resolve(__dirname, '..');
|
||||||
|
|
||||||
|
const NODECG_PORT = Number.parseInt(process.env.NODECG_PORT ?? '9090', 10);
|
||||||
|
const NODECG_HOST = process.env.NODECG_HOST ?? '127.0.0.1';
|
||||||
|
const NODECG_URL = `http://${NODECG_HOST}:${NODECG_PORT}`;
|
||||||
|
|
||||||
|
let nodecgProcess;
|
||||||
|
|
||||||
|
function getNodecgCliPath() {
|
||||||
|
return app.isPackaged
|
||||||
|
? path.join(process.resourcesPath, 'app.asar.unpacked', 'node_modules', 'nodecg', 'bin', 'nodecg.js')
|
||||||
|
: path.join(__dirname, 'node_modules', 'nodecg', 'bin', 'nodecg.js');
|
||||||
|
}
|
||||||
|
|
||||||
|
function startNodecg() {
|
||||||
|
const nodecgCli = getNodecgCliPath();
|
||||||
|
|
||||||
|
nodecgProcess = spawn(process.execPath, [nodecgCli, 'start'], {
|
||||||
|
cwd: BUNDLE_ROOT,
|
||||||
|
env: {
|
||||||
|
...process.env,
|
||||||
|
ELECTRON_RUN_AS_NODE: '1',
|
||||||
|
NODECG_PORT: String(NODECG_PORT),
|
||||||
|
},
|
||||||
|
stdio: 'inherit',
|
||||||
|
});
|
||||||
|
|
||||||
|
nodecgProcess.on('exit', (code) => {
|
||||||
|
if (code !== 0) {
|
||||||
|
console.error(`[electron] NodeCG process exited with code ${code}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!app.isQuitting) {
|
||||||
|
app.quit();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopNodecg() {
|
||||||
|
if (!nodecgProcess || nodecgProcess.killed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
app.isQuitting = true;
|
||||||
|
nodecgProcess.kill();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function waitForNodecg(retries = 80, delayMs = 250) {
|
||||||
|
for (let attempt = 0; attempt < retries; attempt += 1) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(NODECG_URL, { method: 'HEAD' });
|
||||||
|
if (response.ok || response.status >= 300) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Ignore connection errors while NodeCG boots.
|
||||||
|
}
|
||||||
|
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
setTimeout(resolve, delayMs);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`NodeCG did not start in time at ${NODECG_URL}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createWindow() {
|
||||||
|
const mainWindow = new BrowserWindow({
|
||||||
|
width: 1366,
|
||||||
|
height: 768,
|
||||||
|
autoHideMenuBar: true,
|
||||||
|
webPreferences: {
|
||||||
|
contextIsolation: true,
|
||||||
|
sandbox: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await mainWindow.loadURL(NODECG_URL);
|
||||||
|
}
|
||||||
|
|
||||||
|
app.whenReady().then(async () => {
|
||||||
|
startNodecg();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await waitForNodecg();
|
||||||
|
await createWindow();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[electron] Failed to start wrapper:', error);
|
||||||
|
app.quit();
|
||||||
|
}
|
||||||
|
|
||||||
|
app.on('activate', async () => {
|
||||||
|
if (BrowserWindow.getAllWindows().length === 0) {
|
||||||
|
await createWindow();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
app.on('before-quit', () => {
|
||||||
|
app.isQuitting = true;
|
||||||
|
stopNodecg();
|
||||||
|
});
|
||||||
|
|
||||||
|
app.on('window-all-closed', () => {
|
||||||
|
if (process.platform !== 'darwin') {
|
||||||
|
app.quit();
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
{
|
||||||
|
"name": "scoreko-dev-electron-wrapper",
|
||||||
|
"version": "0.2.0",
|
||||||
|
"private": true,
|
||||||
|
"description": "Electron wrapper for running the scoreko-dev NodeCG bundle on Windows.",
|
||||||
|
"main": "main.mjs",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"start": "electron .",
|
||||||
|
"dist:win": "electron-builder --win nsis"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"electron": "40.6.1",
|
||||||
|
"nodecg": "^2.6.4"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"electron-builder": "^26.0.12"
|
||||||
|
},
|
||||||
|
"build": {
|
||||||
|
"appId": "com.scoreko.dev",
|
||||||
|
"productName": "Scoreko Dev",
|
||||||
|
"compression": "maximum",
|
||||||
|
"asar": true,
|
||||||
|
"directories": {
|
||||||
|
"output": "dist"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"main.mjs",
|
||||||
|
"package.json",
|
||||||
|
"node_modules/**/*"
|
||||||
|
],
|
||||||
|
"extraResources": [
|
||||||
|
{
|
||||||
|
"from": "../",
|
||||||
|
"to": "bundle",
|
||||||
|
"filter": [
|
||||||
|
"package.json",
|
||||||
|
"configschema.json",
|
||||||
|
"LICENSE",
|
||||||
|
"README.md",
|
||||||
|
"schemas/**/*",
|
||||||
|
"dashboard/**/*",
|
||||||
|
"graphics/**/*",
|
||||||
|
"extension/**/*",
|
||||||
|
"src/**/*",
|
||||||
|
"node_modules/**/*",
|
||||||
|
"!electron/**/*",
|
||||||
|
"!.git/**/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"win": {
|
||||||
|
"target": [
|
||||||
|
{
|
||||||
|
"target": "nsis",
|
||||||
|
"arch": [
|
||||||
|
"x64"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nsis": {
|
||||||
|
"oneClick": false,
|
||||||
|
"allowToChangeInstallationDirectory": true,
|
||||||
|
"createDesktopShortcut": true,
|
||||||
|
"createStartMenuShortcut": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+3
-1
@@ -23,7 +23,9 @@
|
|||||||
"lint": "eslint",
|
"lint": "eslint",
|
||||||
"schema-types": "nodecg schema-types",
|
"schema-types": "nodecg schema-types",
|
||||||
"start": "nodecg start",
|
"start": "nodecg start",
|
||||||
"watch": "conc -n B,E -c red,blue -k vite \"tsc -b -w --preserveWatchOutput tsconfig.extension.json\""
|
"watch": "conc -n B,E -c red,blue -k vite \"tsc -b -w --preserveWatchOutput tsconfig.extension.json\"",
|
||||||
|
"electron:start": "pnpm --dir electron start",
|
||||||
|
"electron:dist": "pnpm --dir electron dist:win"
|
||||||
},
|
},
|
||||||
"nodecg": {
|
"nodecg": {
|
||||||
"compatibleRange": "^2.6.0",
|
"compatibleRange": "^2.6.0",
|
||||||
|
|||||||
Reference in New Issue
Block a user