feat: enhance NodeCG runtime management and packaging

- Update .gitignore and .prettierignore to exclude additional cache and configuration files.
- Revise README.md for clarity on build processes and runtime behavior.
- Improve architecture documentation to reflect changes in startup flow and module responsibilities.
- Modify troubleshooting guide to address common runtime issues and installation steps.
- Enhance ESLint configuration to ignore more directories.
- Update package.json scripts for better build and distribution processes.
- Introduce build-scoreko-bundle.mjs for building the Scoreko bundle.
- Implement prepare-nodecg-runtime.mjs for managing NodeCG runtime installation and updates.
- Add runtime-provisioner.ts to handle user-specific NodeCG runtime provisioning.
- Create tests for runtime provisioning to ensure correct behavior.
- Refactor process-manager.ts and main.ts to integrate new runtime management logic.
This commit is contained in:
2026-05-09 17:45:36 +02:00
parent b10b8adb98
commit 41e4e91c4b
16 changed files with 737 additions and 100 deletions
+22 -12
View File
@@ -3,7 +3,8 @@ import path from "node:path";
import { getRuntimeConfig } from "./config/runtime-config";
import { showFatalError, log } from "./errors/error-presenter";
import { createNodecgProcessManager } from "./nodecg/process-manager";
import { createNodecgProcessManager, NodecgProcessManager } from "./nodecg/process-manager";
import { prepareUserNodecgRuntime } from "./nodecg/runtime-provisioner";
import { getRemainingDelayMs } from "./utils/timing";
import { createLoadingWindow, createMainWindow } from "./windows/window-factory";
@@ -15,7 +16,7 @@ app.setPath("userData", path.join(app.getPath("appData"), appConfig.userDataDire
const isDev = !app.isPackaged;
const rootPath = isDev ? path.resolve(__dirname, "../..") : process.resourcesPath;
const nodecgRootPath = path.resolve(rootPath, "lib", "nodecg");
const sourceNodecgRuntimePath = path.resolve(rootPath, "lib", "nodecg");
const mainDashboardUrl = `http://localhost:${appConfig.nodecgPort}/bundles/${appConfig.bundleName}/${appConfig.mainDashboardRoute}`;
const loadingDashboardUrl = `http://localhost:${appConfig.nodecgPort}/bundles/${appConfig.bundleName}/${appConfig.loadingDashboardRoute}`;
const nodecgBaseUrl = `http://127.0.0.1:${appConfig.nodecgPort}`;
@@ -26,18 +27,11 @@ if (!hasSingleInstanceLock) {
app.quit();
}
const nodecgManager = createNodecgProcessManager({
isDev,
nodecgRootPath,
nodecgBaseUrl,
appConfig,
log,
});
type AppShutdownState = "running" | "stopping" | "stopped";
let mainWindow: BrowserWindow | null = null;
let loadingWindow: BrowserWindow | null = null;
let nodecgManager: NodecgProcessManager | null = null;
let shutdownState: AppShutdownState = "running";
function focusExistingWindow(): void {
@@ -56,6 +50,22 @@ function focusExistingWindow(): void {
}
async function launchApplication(): Promise<void> {
const nodecgRootPath = prepareUserNodecgRuntime({
sourceRuntimePath: sourceNodecgRuntimePath,
userDataPath: app.getPath("userData"),
appVersion: app.getVersion(),
bundleName: appConfig.bundleName,
log,
});
nodecgManager = createNodecgProcessManager({
isDev,
nodecgRootPath,
nodecgBaseUrl,
appConfig,
log,
});
// We create both windows early so startup feels instant while NodeCG is booting in the background.
mainWindow = createMainWindow({ appConfig, rootPath, mainDashboardUrl });
loadingWindow = createLoadingWindow({ appConfig, rootPath });
@@ -110,12 +120,12 @@ function stopNodecgGracefully(): Promise<void> {
}
if (shutdownState === "stopping") {
return nodecgManager.stopNodecgProcessGracefully();
return nodecgManager?.stopNodecgProcessGracefully() ?? Promise.resolve();
}
shutdownState = "stopping";
return nodecgManager.stopNodecgProcessGracefully().finally(() => {
return (nodecgManager?.stopNodecgProcessGracefully() ?? Promise.resolve()).finally(() => {
shutdownState = "stopped";
});
}