feat: improve NodeCG runtime installation and relaunch behavior

This commit is contained in:
2026-05-16 22:22:30 +02:00
parent 41e4e91c4b
commit 955a1f7116
10 changed files with 104 additions and 37 deletions
+19 -5
View File
@@ -50,7 +50,7 @@ function focusExistingWindow(): void {
}
async function launchApplication(): Promise<void> {
const nodecgRootPath = prepareUserNodecgRuntime({
const preparedRuntime = prepareUserNodecgRuntime({
sourceRuntimePath: sourceNodecgRuntimePath,
userDataPath: app.getPath("userData"),
appVersion: app.getVersion(),
@@ -58,9 +58,16 @@ async function launchApplication(): Promise<void> {
log,
});
if (preparedRuntime.installed && app.isPackaged) {
log("Runtime was installed or refreshed; relaunching Scoreko before starting NodeCG.");
app.relaunch();
app.exit(0);
return;
}
nodecgManager = createNodecgProcessManager({
isDev,
nodecgRootPath,
nodecgRootPath: preparedRuntime.runtimePath,
nodecgBaseUrl,
appConfig,
log,
@@ -70,9 +77,7 @@ async function launchApplication(): Promise<void> {
mainWindow = createMainWindow({ appConfig, rootPath, mainDashboardUrl });
loadingWindow = createLoadingWindow({ appConfig, rootPath });
await nodecgManager.startNodecgProcess();
await nodecgManager.waitForNodecgReady(Date.now());
await startNodecg();
if (!loadingWindow || loadingWindow.isDestroyed()) {
return;
@@ -99,6 +104,15 @@ async function launchApplication(): Promise<void> {
closeLoadingWindow();
}
async function startNodecg(): Promise<void> {
if (!nodecgManager) {
throw new Error("NodeCG process manager is not initialized.");
}
await nodecgManager.startNodecgProcess();
await nodecgManager.waitForNodecgReady(Date.now());
}
function sleep(ms: number): Promise<void> {
return new Promise((resolve) => {
setTimeout(resolve, ms);
+11 -1
View File
@@ -80,7 +80,8 @@ export function createNodecgProcessManager({
},
stdio: ["ignore", "pipe", "pipe"],
detached: resolvedDeps.platform !== "win32",
shell: resolvedDeps.platform === "win32",
shell: false,
windowsHide: true,
});
child.stdout?.on("data", (chunk) => {
@@ -163,7 +164,15 @@ export function createNodecgProcessManager({
killNodecgProcessTree(pid, "SIGTERM", log, resolvedDeps);
stopNodecgPromise = new Promise((resolve) => {
let completed = false;
const complete = () => {
if (completed) {
return;
}
completed = true;
if (nodecgProcess === processToStop) {
nodecgProcess = null;
}
@@ -181,6 +190,7 @@ export function createNodecgProcessManager({
if (processToStop.exitCode === null && processToStop.signalCode === null) {
log(`NodeCG did not exit after SIGTERM, forcing SIGKILL pid=${pid}`);
killNodecgProcessTree(pid, "SIGKILL", log, resolvedDeps);
complete();
}
},
Math.max(0, appConfig.nodecgKillTimeoutMs),
+14 -4
View File
@@ -28,6 +28,11 @@ type RuntimeProvisionerDeps = {
writeFileSync: (filePath: string, content: string) => unknown;
};
export type PreparedNodecgRuntime = {
runtimePath: string;
installed: boolean;
};
type RuntimeManifest = {
appVersion?: unknown;
bundleName?: unknown;
@@ -47,14 +52,16 @@ export function prepareUserNodecgRuntime({
bundleName,
log,
deps,
}: RuntimeProvisionerConfig): string {
}: RuntimeProvisionerConfig): PreparedNodecgRuntime {
const resolvedDeps = resolveDeps(deps);
const targetRuntimePath = path.join(userDataPath, "nodecg");
validateSourceRuntime(sourceRuntimePath, bundleName, resolvedDeps.existsSync);
resolvedDeps.mkdirSync(targetRuntimePath, { recursive: true });
if (shouldInstallRuntime(sourceRuntimePath, targetRuntimePath, appVersion, bundleName, resolvedDeps)) {
const installed = shouldInstallRuntime(sourceRuntimePath, targetRuntimePath, appVersion, bundleName, resolvedDeps);
if (installed) {
log(`Installing managed NodeCG runtime into ${targetRuntimePath}`);
installManagedRuntime(sourceRuntimePath, targetRuntimePath, appVersion, bundleName, resolvedDeps);
}
@@ -63,7 +70,7 @@ export function prepareUserNodecgRuntime({
resolvedDeps.mkdirSync(path.join(targetRuntimePath, writableDir), { recursive: true });
}
return targetRuntimePath;
return { runtimePath: targetRuntimePath, installed };
}
function resolveDeps(deps?: Partial<RuntimeProvisionerDeps>): RuntimeProvisionerDeps {
@@ -166,7 +173,10 @@ function installManagedRuntime(
);
}
function readJson(filePath: string, deps: Pick<RuntimeProvisionerDeps, "existsSync" | "readFileSync">): RuntimeManifest | null {
function readJson(
filePath: string,
deps: Pick<RuntimeProvisionerDeps, "existsSync" | "readFileSync">,
): RuntimeManifest | null {
if (!deps.existsSync(filePath)) {
return null;
}