mirror of
https://github.com/Pandipipas/scoreko-electron-dev.git
synced 2026-06-06 05:32:06 +00:00
Cleanup final
This commit is contained in:
@@ -2,6 +2,7 @@ import { AppRuntimeConfig } from "../config/runtime-config";
|
||||
import { NodecgProcessManager } from "../nodecg/process-manager";
|
||||
import { PreparedNodecgRuntime } from "../nodecg/runtime-provisioner";
|
||||
import { getRemainingDelayMs } from "../utils/timing";
|
||||
import { ApplicationPaths } from "./paths";
|
||||
import { createShutdownService, ShutdownService } from "./shutdown-service";
|
||||
|
||||
export type ApplicationState = "idle" | "preparing" | "starting" | "ready" | "stopping" | "stopped" | "failed";
|
||||
@@ -21,14 +22,7 @@ export type ApplicationControllerConfig = {
|
||||
appVersion: string;
|
||||
isPackaged: boolean;
|
||||
isWindows: boolean;
|
||||
paths: {
|
||||
rootPath: string;
|
||||
sourceNodecgRuntimePath: string;
|
||||
userDataPath: string;
|
||||
nodecgBaseUrl: string;
|
||||
mainDashboardUrl: string;
|
||||
loadingDashboardUrl: string;
|
||||
};
|
||||
paths: ApplicationPaths;
|
||||
deps: {
|
||||
createLoadingWindow: () => ApplicationWindow;
|
||||
createMainWindow: () => ApplicationWindow;
|
||||
|
||||
@@ -53,16 +53,6 @@ export function getEnv(name: string, fallback: string): string {
|
||||
return getOptionalEnv(name) ?? fallback;
|
||||
}
|
||||
|
||||
export 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;
|
||||
}
|
||||
|
||||
export function parseEnvIntInRange(name: string, fallback: number, min: number, max: number): number {
|
||||
// We throw here instead of silently coercing to avoid hidden misconfiguration in production.
|
||||
const rawValue = process.env[name];
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
import { ChildProcess, SpawnOptions } from "node:child_process";
|
||||
import { SpawnOptions } from "node:child_process";
|
||||
|
||||
export type PlatformProcessKillerDeps = {
|
||||
platform: NodeJS.Platform;
|
||||
spawnProcess: (command: string, args: string[], options: SpawnOptions) => ChildProcess;
|
||||
spawnProcess: (command: string, args: string[], options: SpawnOptions) => SpawnedKillerProcess;
|
||||
killProcess: (pid: number, signal: NodeJS.Signals) => void;
|
||||
log: (...args: unknown[]) => void;
|
||||
};
|
||||
|
||||
type SpawnedKillerProcess = {
|
||||
on: (event: "error", listener: (error: Error) => void) => unknown;
|
||||
};
|
||||
|
||||
export function killProcessTree(pid: number, signal: NodeJS.Signals, deps: PlatformProcessKillerDeps): boolean {
|
||||
if (!Number.isSafeInteger(pid) || pid <= 0) {
|
||||
deps.log(`Invalid pid for process tree termination: ${pid}`);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ChildProcess, spawn, SpawnOptions } from "node:child_process";
|
||||
import { spawn, SpawnOptions } from "node:child_process";
|
||||
import fs from "node:fs";
|
||||
import net from "node:net";
|
||||
import path from "node:path";
|
||||
@@ -17,7 +17,7 @@ type NodecgProcessManagerConfig = {
|
||||
};
|
||||
|
||||
type NodecgProcessManagerDeps = {
|
||||
spawnProcess: (command: string, args: string[], options: SpawnOptions) => ChildProcess;
|
||||
spawnProcess: (command: string, args: string[], options: SpawnOptions) => NodecgChildProcess;
|
||||
pathExists: (candidatePath: string) => boolean;
|
||||
fetchUrl: typeof fetch;
|
||||
platform: NodeJS.Platform;
|
||||
@@ -31,6 +31,22 @@ type NodecgProcessManagerDeps = {
|
||||
hasReadWriteAccess: (candidatePath: string) => boolean;
|
||||
};
|
||||
|
||||
type NodecgChildProcess = {
|
||||
pid?: number;
|
||||
killed: boolean;
|
||||
exitCode: number | null;
|
||||
signalCode: NodeJS.Signals | null;
|
||||
stdout?: ProcessOutputStream | null;
|
||||
stderr?: ProcessOutputStream | null;
|
||||
on(event: "exit", listener: (code: number | null, signal: NodeJS.Signals | null) => void): unknown;
|
||||
on(event: "error", listener: (error: Error) => void): unknown;
|
||||
once(event: "exit", listener: () => void): unknown;
|
||||
};
|
||||
|
||||
type ProcessOutputStream = {
|
||||
on(event: "data", listener: (chunk: unknown) => void): unknown;
|
||||
};
|
||||
|
||||
export type NodecgProcessManager = {
|
||||
startNodecgProcess: () => Promise<void>;
|
||||
waitForNodecgReady: (startTime: number) => Promise<void>;
|
||||
@@ -50,7 +66,7 @@ export function createNodecgProcessManager({
|
||||
}: NodecgProcessManagerConfig): NodecgProcessManager {
|
||||
const resolvedDeps = resolveDeps(deps);
|
||||
|
||||
let nodecgProcess: ChildProcess | null = null;
|
||||
let nodecgProcess: NodecgChildProcess | null = null;
|
||||
let nodecgState: NodecgProcessState = "idle";
|
||||
let startNodecgPromise: Promise<void> | null = null;
|
||||
let stopNodecgPromise: Promise<void> | null = null;
|
||||
|
||||
@@ -2,6 +2,7 @@ import fs from "node:fs";
|
||||
|
||||
import { getDefaultUpdateConfigPath } from "../app/paths";
|
||||
import { AppRuntimeConfig } from "../config/runtime-config";
|
||||
import { isRecord, readNonEmptyString } from "../utils/unknown-values";
|
||||
import { UpdateFileConfig, validateHttpUrl } from "./update-schema";
|
||||
|
||||
const DEFAULT_UPDATE_ASSET_PATTERN = "Scoreko-setup-.*\\.exe$";
|
||||
@@ -17,8 +18,17 @@ type UpdateConfigOptions = {
|
||||
allowInsecureHttp: boolean;
|
||||
};
|
||||
|
||||
type UpdateRuntimeConfig = Pick<
|
||||
AppRuntimeConfig,
|
||||
| "updateApiUrl"
|
||||
| "updateAssetPattern"
|
||||
| "updateConfigPathOverride"
|
||||
| "updateReleasePageUrl"
|
||||
| "updatesEnabled"
|
||||
>;
|
||||
|
||||
export function loadUpdateSettings(
|
||||
appConfig: AppRuntimeConfig,
|
||||
appConfig: UpdateRuntimeConfig,
|
||||
rootPath: string,
|
||||
log: (...args: unknown[]) => void,
|
||||
options: UpdateConfigOptions = { allowInsecureHttp: true },
|
||||
@@ -32,12 +42,12 @@ export function loadUpdateSettings(
|
||||
...(apiUrl ? { apiUrl } : {}),
|
||||
...(releasePageUrl ? { releasePageUrl } : {}),
|
||||
assetPattern:
|
||||
appConfig.updateAssetPattern || readOptionalString(fileConfig.assetPattern) || DEFAULT_UPDATE_ASSET_PATTERN,
|
||||
appConfig.updateAssetPattern || readNonEmptyString(fileConfig.assetPattern) || DEFAULT_UPDATE_ASSET_PATTERN,
|
||||
};
|
||||
}
|
||||
|
||||
export function readUpdateFileConfig(
|
||||
appConfig: AppRuntimeConfig,
|
||||
appConfig: Pick<AppRuntimeConfig, "updateConfigPathOverride">,
|
||||
rootPath: string,
|
||||
log: (...args: unknown[]) => void,
|
||||
): UpdateFileConfig {
|
||||
@@ -70,18 +80,10 @@ function normalizeUpdateFileConfig(value: unknown): UpdateFileConfig {
|
||||
}
|
||||
|
||||
function readOptionalHttpUrl(value: unknown, options: UpdateConfigOptions): string | undefined {
|
||||
const rawValue = readOptionalString(value);
|
||||
const rawValue = readNonEmptyString(value);
|
||||
if (!rawValue) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return validateHttpUrl(rawValue, options) ?? undefined;
|
||||
}
|
||||
|
||||
function readOptionalString(value: unknown): string | undefined {
|
||||
return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { BrowserWindow, dialog } from "electron";
|
||||
import type { MessageBoxOptions } from "electron";
|
||||
import type { MessageBoxOptions, MessageBoxReturnValue } from "electron";
|
||||
|
||||
import { ReleaseUpdate } from "./update-schema";
|
||||
|
||||
@@ -41,6 +41,9 @@ export async function askToInstallUpdate(update: ReleaseUpdate, parentWindow: Br
|
||||
return result.response === 0;
|
||||
}
|
||||
|
||||
function showMessageBox(parentWindow: BrowserWindow | null, options: MessageBoxOptions) {
|
||||
function showMessageBox(
|
||||
parentWindow: BrowserWindow | null,
|
||||
options: MessageBoxOptions,
|
||||
): Promise<MessageBoxReturnValue> {
|
||||
return parentWindow ? dialog.showMessageBox(parentWindow, options) : dialog.showMessageBox(options);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { isRecord, readNonEmptyString } from "../utils/unknown-values";
|
||||
|
||||
export type GiteaReleaseAsset = {
|
||||
name: string;
|
||||
browserDownloadUrl: string;
|
||||
@@ -47,11 +49,14 @@ export function parseGiteaRelease(value: unknown): GiteaRelease | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
const title = readNonEmptyString(value.name);
|
||||
const pageUrl = readOptionalUrlString(value.html_url);
|
||||
|
||||
return {
|
||||
tagName,
|
||||
assets,
|
||||
...(readOptionalString(value.name) ? { title: readOptionalString(value.name) } : {}),
|
||||
...(readOptionalUrlString(value.html_url) ? { pageUrl: readOptionalUrlString(value.html_url) } : {}),
|
||||
...(title ? { title } : {}),
|
||||
...(pageUrl ? { pageUrl } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -177,16 +182,12 @@ function normalizeVersion(version: string): number[] {
|
||||
}
|
||||
|
||||
function readRequiredString(value: unknown): string | null {
|
||||
const text = readOptionalString(value);
|
||||
const text = readNonEmptyString(value);
|
||||
return text && text.length > 0 ? text : null;
|
||||
}
|
||||
|
||||
function readOptionalString(value: unknown): string | undefined {
|
||||
return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
|
||||
}
|
||||
|
||||
function readOptionalUrlString(value: unknown): string | undefined {
|
||||
const rawValue = readOptionalString(value);
|
||||
const rawValue = readNonEmptyString(value);
|
||||
if (!rawValue) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -194,10 +195,6 @@ function readOptionalUrlString(value: unknown): string | undefined {
|
||||
return validateHttpUrl(rawValue, { allowInsecureHttp: true }) ?? undefined;
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
}
|
||||
|
||||
function isPresent<T>(value: T | null): value is T {
|
||||
return value !== null;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
export function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
}
|
||||
|
||||
export function readNonEmptyString(value: unknown): string | undefined {
|
||||
return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
|
||||
}
|
||||
Reference in New Issue
Block a user