mirror of
https://github.com/Pandipipas/scoreko-electron-dev.git
synced 2026-06-06 05:32:06 +00:00
feat: Restore Electron renderer and enhance NodeCG runtime management
This commit is contained in:
@@ -0,0 +1,92 @@
|
|||||||
|
# Phase 1 Fix Summary
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
This fix restores the Electron renderer after the Phase 1 architecture extraction. It keeps the new main-process structure, does not add a custom renderer, preload, or IPC layer, and does not revert the Phase 1 refactor.
|
||||||
|
|
||||||
|
## Root Cause
|
||||||
|
|
||||||
|
Phase 1 moved the real bootstrap module from `src/main/main.ts` to `src/main/app/bootstrap.ts`. After compilation, that changed the runtime `__dirname` from:
|
||||||
|
|
||||||
|
```text
|
||||||
|
dist/main
|
||||||
|
```
|
||||||
|
|
||||||
|
to:
|
||||||
|
|
||||||
|
```text
|
||||||
|
dist/main/app
|
||||||
|
```
|
||||||
|
|
||||||
|
The development root calculation still treated `__dirname` as if it were `dist/main`, so `rootPath` resolved to `dist` instead of the repository root. Electron then looked for the packaged NodeCG runtime at:
|
||||||
|
|
||||||
|
```text
|
||||||
|
dist/lib/nodecg
|
||||||
|
```
|
||||||
|
|
||||||
|
instead of:
|
||||||
|
|
||||||
|
```text
|
||||||
|
lib/nodecg
|
||||||
|
```
|
||||||
|
|
||||||
|
That prevented the main process from preparing and launching the correct NodeCG runtime, leaving the BrowserWindow without a valid dashboard to render.
|
||||||
|
|
||||||
|
During verification, a second compatibility issue appeared in the packaged runtime: the Vite/NodeCG bundle imports generated files from the bundle-level `nodecg` directory, but `prepare-nodecg-runtime.mjs` did not copy that directory into `lib/nodecg/bundles/scoreko-dev`. Without it, NodeCG could start but failed to mount the `scoreko-dev` extension, and dashboard URLs returned 404.
|
||||||
|
|
||||||
|
## Changes Made
|
||||||
|
|
||||||
|
- `src/main/app/bootstrap.ts`
|
||||||
|
- Passes `dist/main` into the path helper by resolving one level above the compiled bootstrap directory.
|
||||||
|
- Keeps path ownership in `src/main/app/paths.ts` and preserves the extracted bootstrap architecture.
|
||||||
|
|
||||||
|
- `scripts/prepare-nodecg-runtime.mjs`
|
||||||
|
- Copies the generated bundle `nodecg` directory into the managed NodeCG runtime.
|
||||||
|
- Treats that directory as required runtime output so an incomplete Vite/NodeCG build fails early.
|
||||||
|
|
||||||
|
- `src/main/nodecg/runtime-provisioner.ts`
|
||||||
|
- Refreshes the managed runtime when the source runtime manifest was regenerated, even if the app and bundle versions are unchanged.
|
||||||
|
- Still preserves user-owned `cfg`, `db`, and `logs`.
|
||||||
|
|
||||||
|
- `src/tests/runtime-provisioner.test.ts`
|
||||||
|
- Adds coverage for refreshing managed runtime files when the source runtime manifest changes.
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
Commands run successfully:
|
||||||
|
|
||||||
|
```text
|
||||||
|
npm run typecheck
|
||||||
|
npm test
|
||||||
|
npm run lint
|
||||||
|
```
|
||||||
|
|
||||||
|
Current test result:
|
||||||
|
|
||||||
|
```text
|
||||||
|
53 tests passing
|
||||||
|
```
|
||||||
|
|
||||||
|
Runtime verification:
|
||||||
|
|
||||||
|
- Rebuilt the NodeCG runtime.
|
||||||
|
- Rebuilt `better-sqlite3` for Electron 39.5.1.
|
||||||
|
- Started Electron with updates disabled and load delay set to zero.
|
||||||
|
- Confirmed NodeCG served:
|
||||||
|
- `http://127.0.0.1:9090` with `200 OK`
|
||||||
|
- `http://localhost:9090/bundles/scoreko-dev/dashboard/scoreko-dev/main.html?standalone=true` with `200 OK`
|
||||||
|
- `http://localhost:9090/bundles/scoreko-dev/dashboard/loading/main.html?standalone=true` with `200 OK`
|
||||||
|
- Confirmed the Electron renderer target loaded:
|
||||||
|
- title: `Dashboard`
|
||||||
|
- URL: `http://localhost:9090/bundles/scoreko-dev/dashboard/scoreko-dev/main.html?standalone=true#/`
|
||||||
|
- DOM element count: `311`
|
||||||
|
- visible body text included the Scoreko dashboard navigation and controls.
|
||||||
|
|
||||||
|
## Architecture Notes
|
||||||
|
|
||||||
|
- No preload was added.
|
||||||
|
- No IPC was added.
|
||||||
|
- No custom renderer architecture was added.
|
||||||
|
- BrowserWindow security settings remain explicit.
|
||||||
|
- NodeCG remains owned by the main process.
|
||||||
|
- Dashboard loading remains gated behind NodeCG readiness.
|
||||||
@@ -15,6 +15,7 @@ const bundleEntries = [
|
|||||||
"dashboard",
|
"dashboard",
|
||||||
"extension",
|
"extension",
|
||||||
"graphics",
|
"graphics",
|
||||||
|
"nodecg",
|
||||||
"schemas",
|
"schemas",
|
||||||
"shared",
|
"shared",
|
||||||
"configschema.json",
|
"configschema.json",
|
||||||
@@ -23,7 +24,7 @@ const bundleEntries = [
|
|||||||
"README.md",
|
"README.md",
|
||||||
];
|
];
|
||||||
|
|
||||||
const requiredBundleEntries = ["dashboard", "extension", "graphics", "schemas", "shared", "package.json"];
|
const requiredBundleEntries = ["dashboard", "extension", "graphics", "nodecg", "schemas", "shared", "package.json"];
|
||||||
|
|
||||||
function readJson(filePath) {
|
function readJson(filePath) {
|
||||||
return JSON.parse(readFileSync(filePath, "utf8"));
|
return JSON.parse(readFileSync(filePath, "utf8"));
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { app, BrowserWindow } from "electron";
|
import { app, BrowserWindow } from "electron";
|
||||||
|
import path from "node:path";
|
||||||
|
|
||||||
import { getRuntimeConfig } from "../config/runtime-config";
|
import { getRuntimeConfig } from "../config/runtime-config";
|
||||||
import { showFatalError, log } from "../errors/error-presenter";
|
import { showFatalError, log } from "../errors/error-presenter";
|
||||||
@@ -15,7 +16,7 @@ export function bootstrap(): void {
|
|||||||
const paths = getApplicationPaths({
|
const paths = getApplicationPaths({
|
||||||
appConfig,
|
appConfig,
|
||||||
appDataPath: app.getPath("appData"),
|
appDataPath: app.getPath("appData"),
|
||||||
compiledMainDir: __dirname,
|
compiledMainDir: path.resolve(__dirname, ".."),
|
||||||
isDev,
|
isDev,
|
||||||
resourcesPath: process.resourcesPath,
|
resourcesPath: process.resourcesPath,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ type RuntimeManifest = {
|
|||||||
bundleName?: unknown;
|
bundleName?: unknown;
|
||||||
sourceRuntime?: RuntimeManifest | null;
|
sourceRuntime?: RuntimeManifest | null;
|
||||||
bundleVersion?: unknown;
|
bundleVersion?: unknown;
|
||||||
|
generatedAt?: unknown;
|
||||||
nodecgVersion?: unknown;
|
nodecgVersion?: unknown;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -131,6 +132,7 @@ function shouldInstallRuntime(
|
|||||||
targetMarker?.appVersion !== appVersion ||
|
targetMarker?.appVersion !== appVersion ||
|
||||||
targetMarker?.bundleName !== bundleName ||
|
targetMarker?.bundleName !== bundleName ||
|
||||||
targetMarker?.sourceRuntime?.bundleVersion !== sourceMarker?.bundleVersion ||
|
targetMarker?.sourceRuntime?.bundleVersion !== sourceMarker?.bundleVersion ||
|
||||||
|
targetMarker?.sourceRuntime?.generatedAt !== sourceMarker?.generatedAt ||
|
||||||
targetMarker?.sourceRuntime?.nodecgVersion !== sourceMarker?.nodecgVersion
|
targetMarker?.sourceRuntime?.nodecgVersion !== sourceMarker?.nodecgVersion
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ test("prepareUserNodecgRuntime keeps an up-to-date runtime in place", () => {
|
|||||||
const source = path.normalize("/app/lib/nodecg");
|
const source = path.normalize("/app/lib/nodecg");
|
||||||
const userData = path.normalize("/user/scoreko");
|
const userData = path.normalize("/user/scoreko");
|
||||||
const target = path.join(userData, "nodecg");
|
const target = path.join(userData, "nodecg");
|
||||||
const sourceManifest = { bundleVersion: "0.1.0", nodecgVersion: "2.6.4" };
|
const sourceManifest = { bundleVersion: "0.1.0", generatedAt: "2026-05-24T00:00:00.000Z", nodecgVersion: "2.6.4" };
|
||||||
const targetManifest = { appVersion: "0.1.0", bundleName: "scoreko-dev", sourceRuntime: sourceManifest };
|
const targetManifest = { appVersion: "0.1.0", bundleName: "scoreko-dev", sourceRuntime: sourceManifest };
|
||||||
const { state, deps } = createFakeFs(
|
const { state, deps } = createFakeFs(
|
||||||
[
|
[
|
||||||
@@ -125,7 +125,7 @@ test("prepareUserNodecgRuntime refreshes managed files when the app version chan
|
|||||||
const source = path.normalize("/app/lib/nodecg");
|
const source = path.normalize("/app/lib/nodecg");
|
||||||
const userData = path.normalize("/user/scoreko");
|
const userData = path.normalize("/user/scoreko");
|
||||||
const target = path.join(userData, "nodecg");
|
const target = path.join(userData, "nodecg");
|
||||||
const sourceManifest = { bundleVersion: "0.1.0", nodecgVersion: "2.6.4" };
|
const sourceManifest = { bundleVersion: "0.1.0", generatedAt: "2026-05-24T00:00:00.000Z", nodecgVersion: "2.6.4" };
|
||||||
const targetManifest = { appVersion: "0.0.9", bundleName: "scoreko-dev", sourceRuntime: sourceManifest };
|
const targetManifest = { appVersion: "0.0.9", bundleName: "scoreko-dev", sourceRuntime: sourceManifest };
|
||||||
const { state, deps } = createFakeFs(
|
const { state, deps } = createFakeFs(
|
||||||
[
|
[
|
||||||
@@ -155,3 +155,42 @@ test("prepareUserNodecgRuntime refreshes managed files when the app version chan
|
|||||||
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")));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("prepareUserNodecgRuntime refreshes managed files when the source runtime was regenerated", () => {
|
||||||
|
const source = path.normalize("/app/lib/nodecg");
|
||||||
|
const userData = path.normalize("/user/scoreko");
|
||||||
|
const target = path.join(userData, "nodecg");
|
||||||
|
const sourceManifest = { bundleVersion: "0.1.0", generatedAt: "2026-05-24T01:00:00.000Z", nodecgVersion: "2.6.4" };
|
||||||
|
const targetSourceManifest = {
|
||||||
|
bundleVersion: "0.1.0",
|
||||||
|
generatedAt: "2026-05-24T00:00:00.000Z",
|
||||||
|
nodecgVersion: "2.6.4",
|
||||||
|
};
|
||||||
|
const targetManifest = { appVersion: "0.1.0", bundleName: "scoreko-dev", sourceRuntime: targetSourceManifest };
|
||||||
|
const { state, deps } = createFakeFs(
|
||||||
|
[
|
||||||
|
...getSourcePaths(source),
|
||||||
|
path.join(target, "node_modules", "nodecg", "dist", "server", "bootstrap.js"),
|
||||||
|
path.join(target, "bundles", "scoreko-dev", "package.json"),
|
||||||
|
path.join(target, ".scoreko-installed-runtime.json"),
|
||||||
|
],
|
||||||
|
{
|
||||||
|
[path.join(source, ".scoreko-runtime.json")]: JSON.stringify(sourceManifest),
|
||||||
|
[path.join(target, ".scoreko-installed-runtime.json")]: JSON.stringify(targetManifest),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const preparedRuntime = prepareUserNodecgRuntime({
|
||||||
|
sourceRuntimePath: source,
|
||||||
|
userDataPath: userData,
|
||||||
|
appVersion: "0.1.0",
|
||||||
|
bundleName: "scoreko-dev",
|
||||||
|
log: () => undefined,
|
||||||
|
deps,
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(preparedRuntime.installed, true);
|
||||||
|
assert.equal(state.copied.length, 1);
|
||||||
|
assert.ok(state.removed.includes(path.join(target, "bundles")));
|
||||||
|
assert.ok(!state.removed.includes(path.join(target, "cfg")));
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user