Enhance application controller and runtime provisioner with loading window visibility and improved file handling

This commit is contained in:
2026-06-04 01:28:28 +02:00
parent beb22cb438
commit 0ea4c6e01b
4 changed files with 43 additions and 21 deletions
+2 -1
View File
@@ -134,6 +134,8 @@ export function createApplicationController({
mainWindow = deps.createMainWindow(); mainWindow = deps.createMainWindow();
loadingWindow = deps.createLoadingWindow(); loadingWindow = deps.createLoadingWindow();
loadingWindow.show();
state = "starting"; state = "starting";
await startNodecg(); await startNodecg();
@@ -143,7 +145,6 @@ export function createApplicationController({
} }
await loadingWindow.loadURL(paths.loadingDashboardUrl); await loadingWindow.loadURL(paths.loadingDashboardUrl);
loadingWindow.show();
const loadingShownAt = now(); const loadingShownAt = now();
+19 -11
View File
@@ -23,11 +23,13 @@ type RuntimeProvisionerDeps = {
recursive: true; recursive: true;
force: true; force: true;
dereference: true; dereference: true;
filter: (sourcePath: string) => boolean; filter?: (sourcePath: string) => boolean;
}, },
) => unknown; ) => unknown;
readFileSync: (filePath: string) => string | Buffer; readFileSync: (filePath: string) => string | Buffer;
writeFileSync: (filePath: string, content: string) => unknown; writeFileSync: (filePath: string, content: string) => unknown;
statSync: (filePath: string) => { isDirectory: () => boolean };
symlinkSync: (target: string, path: string, type: "junction") => unknown;
}; };
export type PreparedNodecgRuntime = { export type PreparedNodecgRuntime = {
@@ -84,6 +86,8 @@ function resolveDeps(deps?: Partial<RuntimeProvisionerDeps>): RuntimeProvisioner
cpSync: deps?.cpSync ?? fs.cpSync, cpSync: deps?.cpSync ?? fs.cpSync,
readFileSync: deps?.readFileSync ?? fs.readFileSync, readFileSync: deps?.readFileSync ?? fs.readFileSync,
writeFileSync: deps?.writeFileSync ?? fs.writeFileSync, writeFileSync: deps?.writeFileSync ?? fs.writeFileSync,
statSync: deps?.statSync ?? fs.statSync,
symlinkSync: deps?.symlinkSync ?? fs.symlinkSync,
}; };
} }
@@ -150,16 +154,20 @@ function installManagedRuntime(
deps.rmSync(path.join(targetRuntimePath, entry), { recursive: true, force: true }); deps.rmSync(path.join(targetRuntimePath, entry), { recursive: true, force: true });
} }
deps.cpSync(sourceRuntimePath, targetRuntimePath, { for (const entry of MANAGED_RUNTIME_ENTRIES) {
recursive: true, const sourcePath = path.join(sourceRuntimePath, entry);
force: true, const targetPath = path.join(targetRuntimePath, entry);
dereference: true,
filter: (sourcePath) => { if (!deps.existsSync(sourcePath)) {
const relativePath = path.relative(sourceRuntimePath, sourcePath); continue;
const firstSegment = relativePath.split(path.sep)[0]; }
return !WRITABLE_NODECG_DIRS.includes(firstSegment as (typeof WRITABLE_NODECG_DIRS)[number]);
}, if (deps.statSync(sourcePath).isDirectory()) {
}); deps.symlinkSync(sourcePath, targetPath, "junction");
} else {
deps.cpSync(sourcePath, targetPath, { recursive: true, force: true, dereference: true });
}
}
const sourceRuntime = readJson(path.join(sourceRuntimePath, ".scoreko-runtime.json"), deps); const sourceRuntime = readJson(path.join(sourceRuntimePath, ".scoreko-runtime.json"), deps);
deps.writeFileSync( deps.writeFileSync(
+2 -2
View File
@@ -133,10 +133,10 @@ test("ApplicationController preserves startup ordering and schedules updates aft
"create-manager", "create-manager",
"create-main", "create-main",
"create-loading", "create-loading",
"loading:show",
"start-nodecg", "start-nodecg",
"wait-nodecg", "wait-nodecg",
`loading:load:${paths.loadingDashboardUrl}`, `loading:load:${paths.loadingDashboardUrl}`,
"loading:show",
`main:load:${paths.mainDashboardUrl}`, `main:load:${paths.mainDashboardUrl}`,
"main:show", "main:show",
"loading:close", "loading:close",
@@ -192,10 +192,10 @@ test("ApplicationController directly launches packaged app after runtime install
"create-manager", "create-manager",
"create-main", "create-main",
"create-loading", "create-loading",
"loading:show",
"start-nodecg", "start-nodecg",
"wait-nodecg", "wait-nodecg",
"loading:load:http://localhost:9090/loading", "loading:load:http://localhost:9090/loading",
"loading:show",
"main:load:http://localhost:9090/main", "main:load:http://localhost:9090/main",
"main:show", "main:show",
"loading:close", "loading:close",
+20 -7
View File
@@ -38,10 +38,21 @@ function createFakeFs(initialPaths: string[] = [], initialFiles: Record<string,
cpSync: (from: string, to: string) => { cpSync: (from: string, to: string) => {
state.copied.push({ from: path.normalize(from), to: path.normalize(to) }); state.copied.push({ from: path.normalize(from), to: path.normalize(to) });
state.paths.add(path.normalize(to)); state.paths.add(path.normalize(to));
state.paths.add(path.join(path.normalize(to), "index.js")); },
state.paths.add(path.join(path.normalize(to), "package.json")); statSync: (filePath: string) => ({
state.paths.add(path.join(path.normalize(to), "node_modules", "nodecg", "dist", "server", "bootstrap.js")); isDirectory: () => {
state.paths.add(path.join(path.normalize(to), "bundles", "scoreko-dev", "package.json")); const normalized = path.normalize(filePath);
return normalized.endsWith("node_modules") || normalized.endsWith("bundles");
},
}),
symlinkSync: (target: string, linkPath: string, type: string) => {
state.copied.push({ from: path.normalize(target), to: path.normalize(linkPath) });
state.paths.add(path.normalize(linkPath));
if (target.endsWith("node_modules")) {
state.paths.add(path.join(path.normalize(linkPath), "nodecg", "dist", "server", "bootstrap.js"));
} else if (target.endsWith("bundles")) {
state.paths.add(path.join(path.normalize(linkPath), "scoreko-dev", "package.json"));
}
}, },
readFileSync: (filePath: string) => state.files.get(path.normalize(filePath)) ?? "{}", readFileSync: (filePath: string) => state.files.get(path.normalize(filePath)) ?? "{}",
writeFileSync: (filePath: string, content: string) => { writeFileSync: (filePath: string, content: string) => {
@@ -57,7 +68,9 @@ function getSourcePaths(source: string) {
source, source,
path.join(source, "index.js"), path.join(source, "index.js"),
path.join(source, "package.json"), path.join(source, "package.json"),
path.join(source, "node_modules"),
path.join(source, "node_modules", "nodecg", "dist", "server", "bootstrap.js"), path.join(source, "node_modules", "nodecg", "dist", "server", "bootstrap.js"),
path.join(source, "bundles"),
path.join(source, "bundles", "scoreko-dev", "package.json"), path.join(source, "bundles", "scoreko-dev", "package.json"),
path.join(source, ".scoreko-runtime.json"), path.join(source, ".scoreko-runtime.json"),
]; ];
@@ -81,7 +94,7 @@ test("prepareUserNodecgRuntime copies the packaged runtime into userData", () =>
assert.equal(preparedRuntime.runtimePath, path.join(userData, "nodecg")); assert.equal(preparedRuntime.runtimePath, path.join(userData, "nodecg"));
assert.equal(preparedRuntime.installed, true); assert.equal(preparedRuntime.installed, true);
assert.equal(state.copied.length, 1); assert.equal(state.copied.length, 4);
assert.ok(state.paths.has(path.join(userData, "nodecg", "cfg"))); assert.ok(state.paths.has(path.join(userData, "nodecg", "cfg")));
assert.ok(state.paths.has(path.join(userData, "nodecg", "db"))); assert.ok(state.paths.has(path.join(userData, "nodecg", "db")));
assert.ok(state.paths.has(path.join(userData, "nodecg", "logs"))); assert.ok(state.paths.has(path.join(userData, "nodecg", "logs")));
@@ -150,7 +163,7 @@ test("prepareUserNodecgRuntime refreshes managed files when the app version chan
}); });
assert.equal(preparedRuntime.installed, true); assert.equal(preparedRuntime.installed, true);
assert.equal(state.copied.length, 1); assert.equal(state.copied.length, 4);
assert.ok(state.removed.includes(path.join(target, "node_modules"))); assert.ok(state.removed.includes(path.join(target, "node_modules")));
assert.ok(state.removed.includes(path.join(target, "bundles"))); assert.ok(state.removed.includes(path.join(target, "bundles")));
assert.ok(!state.removed.includes(path.join(target, "db"))); assert.ok(!state.removed.includes(path.join(target, "db")));
@@ -190,7 +203,7 @@ test("prepareUserNodecgRuntime refreshes managed files when the source runtime w
}); });
assert.equal(preparedRuntime.installed, true); assert.equal(preparedRuntime.installed, true);
assert.equal(state.copied.length, 1); assert.equal(state.copied.length, 4);
assert.ok(state.removed.includes(path.join(target, "bundles"))); assert.ok(state.removed.includes(path.join(target, "bundles")));
assert.ok(!state.removed.includes(path.join(target, "cfg"))); assert.ok(!state.removed.includes(path.join(target, "cfg")));
}); });