mirror of
https://github.com/Pandipipas/scoreko-electron-dev.git
synced 2026-06-05 21:22:07 +00:00
Refactor NodeCG runtime preparation and update handling
- Updated paths and configurations in doctor.mjs and prepare-nodecg-runtime.mjs to use new build-config.mjs imports. - Enhanced runtime installation checks and permissions validation. - Introduced new update configuration management in update-config.ts, including loading and validating update settings. - Implemented update service for managing update checks and downloads in update-service.ts. - Replaced update-utils.ts with update-schema.ts for better structure and clarity in update handling. - Added comprehensive tests for update download and settings management. - Ensured secure handling of download URLs and improved error handling in update processes.
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
import path from "node:path";
|
||||
|
||||
export const electronRoot = process.cwd();
|
||||
export const bundleRoot = path.resolve(electronRoot, "..");
|
||||
export const nodecgRuntimeRoot = path.join(electronRoot, "lib", "nodecg");
|
||||
export const nodecgRuntimeNodeModules = path.join(nodecgRuntimeRoot, "node_modules");
|
||||
export const bundleName = process.env.NODECG_BUNDLE_NAME?.trim() || "scoreko-dev";
|
||||
export const runtimeBundleRoot = path.join(nodecgRuntimeRoot, "bundles", bundleName);
|
||||
export const runtimeNpmCache = process.env.npm_config_cache ?? path.join(electronRoot, ".npm-runtime-cache");
|
||||
export const electronCache = process.env.ELECTRON_CACHE ?? path.join(electronRoot, ".electron-cache");
|
||||
|
||||
export const bundleRootMarkers = ["package.json", "pnpm-lock.yaml"];
|
||||
export const generatedBundleEntries = ["extension", "node_modules/.vite", "shared/dist", "dashboard", "graphics"];
|
||||
export const preparedBundleEntries = [
|
||||
"assets",
|
||||
"dashboard",
|
||||
"extension",
|
||||
"graphics",
|
||||
"nodecg",
|
||||
"schemas",
|
||||
"shared",
|
||||
"configschema.json",
|
||||
"LICENSE",
|
||||
"package.json",
|
||||
"README.md",
|
||||
];
|
||||
export const requiredPreparedBundleEntries = [
|
||||
"dashboard",
|
||||
"extension",
|
||||
"graphics",
|
||||
"nodecg",
|
||||
"schemas",
|
||||
"shared",
|
||||
"package.json",
|
||||
];
|
||||
|
||||
export function getNpmCommand() {
|
||||
return process.platform === "win32" ? "npm.cmd" : "npm";
|
||||
}
|
||||
|
||||
export function getLocalBinPath(commandName) {
|
||||
const extension = process.platform === "win32" ? ".CMD" : "";
|
||||
return path.join(bundleRoot, "node_modules", ".bin", `${commandName}${extension}`);
|
||||
}
|
||||
|
||||
export function getPathInside(rootPath, relativePath) {
|
||||
const resolvedRoot = path.resolve(rootPath);
|
||||
const targetPath = path.resolve(resolvedRoot, relativePath);
|
||||
const pathFromRoot = path.relative(resolvedRoot, targetPath);
|
||||
|
||||
if (!pathFromRoot || pathFromRoot.startsWith("..") || path.isAbsolute(pathFromRoot)) {
|
||||
throw new Error(`Refusing to access path outside ${resolvedRoot}: ${targetPath}`);
|
||||
}
|
||||
|
||||
return targetPath;
|
||||
}
|
||||
@@ -3,14 +3,29 @@ import { existsSync, mkdirSync, rmSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { spawnSync } from "node:child_process";
|
||||
|
||||
const electronRoot = process.cwd();
|
||||
const bundleRoot = path.resolve(electronRoot, "..");
|
||||
const packageJsonPath = path.join(bundleRoot, "package.json");
|
||||
const pnpmLockPath = path.join(bundleRoot, "pnpm-lock.yaml");
|
||||
import {
|
||||
bundleRoot,
|
||||
bundleRootMarkers,
|
||||
electronRoot,
|
||||
generatedBundleEntries,
|
||||
getLocalBinPath,
|
||||
getPathInside,
|
||||
} from "./build-config.mjs";
|
||||
|
||||
const nodeModulesPath = path.join(bundleRoot, "node_modules");
|
||||
|
||||
if (!existsSync(packageJsonPath) || !existsSync(pnpmLockPath)) {
|
||||
console.error(`Scoreko bundle root was not found at: ${bundleRoot}`);
|
||||
const missingMarkers = bundleRootMarkers
|
||||
.map((entry) => path.join(bundleRoot, entry))
|
||||
.filter((candidatePath) => !existsSync(candidatePath));
|
||||
|
||||
if (missingMarkers.length > 0) {
|
||||
console.error(
|
||||
[
|
||||
`Scoreko bundle root was not found at: ${bundleRoot}`,
|
||||
"This Electron package expects to live inside the Scoreko repository with the bundle project as its parent.",
|
||||
...missingMarkers.map((candidatePath) => `Missing: ${candidatePath}`),
|
||||
].join("\n"),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@@ -25,7 +40,6 @@ if (!existsSync(nodeModulesPath)) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const generatedBundleEntries = ["extension", "node_modules/.vite", "shared/dist", "dashboard", "graphics"];
|
||||
const childEnv = {
|
||||
...process.env,
|
||||
COREPACK_HOME: process.env.COREPACK_HOME ?? path.join(electronRoot, ".corepack"),
|
||||
@@ -33,12 +47,7 @@ const childEnv = {
|
||||
};
|
||||
|
||||
function removeGeneratedOutput(relativePath) {
|
||||
const targetPath = path.resolve(bundleRoot, relativePath);
|
||||
|
||||
if (!targetPath.startsWith(`${bundleRoot}${path.sep}`)) {
|
||||
throw new Error(`Refusing to remove path outside the bundle root: ${targetPath}`);
|
||||
}
|
||||
|
||||
const targetPath = getPathInside(bundleRoot, relativePath);
|
||||
rmSync(targetPath, { recursive: true, force: true });
|
||||
}
|
||||
|
||||
@@ -60,11 +69,6 @@ function runCommand(command, args) {
|
||||
}
|
||||
}
|
||||
|
||||
function runLocalBin(commandName, args) {
|
||||
const extension = process.platform === "win32" ? ".CMD" : "";
|
||||
runCommand(path.join(bundleRoot, "node_modules", ".bin", `${commandName}${extension}`), args);
|
||||
}
|
||||
|
||||
for (const entry of generatedBundleEntries) {
|
||||
removeGeneratedOutput(entry);
|
||||
}
|
||||
@@ -73,5 +77,5 @@ for (const entry of ["shared/dist", "dashboard", "graphics", "extension"]) {
|
||||
mkdirSync(path.join(bundleRoot, entry), { recursive: true });
|
||||
}
|
||||
|
||||
runLocalBin("vite", ["build", "--configLoader", "runner"]);
|
||||
runLocalBin("tsc", ["-b", "tsconfig.extension.json"]);
|
||||
runCommand(getLocalBinPath("vite"), ["build", "--configLoader", "runner"]);
|
||||
runCommand(getLocalBinPath("tsc"), ["-b", "tsconfig.extension.json"]);
|
||||
|
||||
+7
-9
@@ -3,8 +3,7 @@ import fs from "node:fs";
|
||||
import net from "node:net";
|
||||
import path from "node:path";
|
||||
|
||||
const cwd = process.cwd();
|
||||
const nodecgRootPath = path.resolve(cwd, "lib", "nodecg");
|
||||
import { bundleName, nodecgRuntimeRoot } from "./build-config.mjs";
|
||||
|
||||
const checks = [];
|
||||
|
||||
@@ -36,20 +35,19 @@ function parseIntInRange(name, fallback, min, max) {
|
||||
}
|
||||
|
||||
function checkNodecgInstall() {
|
||||
const indexPath = path.join(nodecgRootPath, "index.js");
|
||||
const bootstrapPath = path.join(nodecgRootPath, "node_modules", "nodecg", "dist", "server", "bootstrap.js");
|
||||
const manifestPath = path.join(nodecgRootPath, ".scoreko-runtime.json");
|
||||
const bundleName = (process.env.NODECG_BUNDLE_NAME ?? "scoreko-dev").trim();
|
||||
const bundlePath = path.join(nodecgRootPath, "bundles", bundleName);
|
||||
const indexPath = path.join(nodecgRuntimeRoot, "index.js");
|
||||
const bootstrapPath = path.join(nodecgRuntimeRoot, "node_modules", "nodecg", "dist", "server", "bootstrap.js");
|
||||
const manifestPath = path.join(nodecgRuntimeRoot, ".scoreko-runtime.json");
|
||||
const bundlePath = path.join(nodecgRuntimeRoot, "bundles", bundleName);
|
||||
|
||||
addCheck(fs.existsSync(nodecgRootPath), "Packaged NodeCG runtime", nodecgRootPath);
|
||||
addCheck(fs.existsSync(nodecgRuntimeRoot), "Packaged NodeCG runtime", nodecgRuntimeRoot);
|
||||
addCheck(fs.existsSync(indexPath), "Runtime index.js", indexPath);
|
||||
addCheck(fs.existsSync(bootstrapPath), "NodeCG bootstrap", bootstrapPath);
|
||||
addCheck(fs.existsSync(manifestPath), "Runtime manifest", manifestPath);
|
||||
addCheck(fs.existsSync(bundlePath), `Packaged bundle '${bundleName}'`, bundlePath);
|
||||
|
||||
try {
|
||||
fs.accessSync(nodecgRootPath, fs.constants.R_OK | fs.constants.W_OK);
|
||||
fs.accessSync(nodecgRuntimeRoot, fs.constants.R_OK | fs.constants.W_OK);
|
||||
addCheck(true, "lib/nodecg permissions", "Read/write OK for local development");
|
||||
} catch {
|
||||
addCheck(false, "lib/nodecg permissions", "No read/write permissions in lib/nodecg");
|
||||
|
||||
@@ -3,28 +3,17 @@ import { cpSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } fr
|
||||
import path from "node:path";
|
||||
import { spawnSync } from "node:child_process";
|
||||
|
||||
const electronRoot = process.cwd();
|
||||
const bundleRoot = path.resolve(electronRoot, "..");
|
||||
const runtimeRoot = path.join(electronRoot, "lib", "nodecg");
|
||||
const runtimeNodeModules = path.join(runtimeRoot, "node_modules");
|
||||
const bundleName = process.env.NODECG_BUNDLE_NAME?.trim() || "scoreko-dev";
|
||||
const runtimeBundleRoot = path.join(runtimeRoot, "bundles", bundleName);
|
||||
|
||||
const bundleEntries = [
|
||||
"assets",
|
||||
"dashboard",
|
||||
"extension",
|
||||
"graphics",
|
||||
"nodecg",
|
||||
"schemas",
|
||||
"shared",
|
||||
"configschema.json",
|
||||
"LICENSE",
|
||||
"package.json",
|
||||
"README.md",
|
||||
];
|
||||
|
||||
const requiredBundleEntries = ["dashboard", "extension", "graphics", "nodecg", "schemas", "shared", "package.json"];
|
||||
import {
|
||||
bundleName,
|
||||
bundleRoot,
|
||||
getNpmCommand,
|
||||
nodecgRuntimeNodeModules,
|
||||
nodecgRuntimeRoot,
|
||||
preparedBundleEntries,
|
||||
requiredPreparedBundleEntries,
|
||||
runtimeBundleRoot,
|
||||
runtimeNpmCache,
|
||||
} from "./build-config.mjs";
|
||||
|
||||
function readJson(filePath) {
|
||||
return JSON.parse(readFileSync(filePath, "utf8"));
|
||||
@@ -51,7 +40,7 @@ function run(command, args, cwd) {
|
||||
shell: process.platform === "win32",
|
||||
env: {
|
||||
...process.env,
|
||||
npm_config_cache: process.env.npm_config_cache ?? path.join(electronRoot, ".npm-runtime-cache"),
|
||||
npm_config_cache: runtimeNpmCache,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -81,7 +70,7 @@ function getInstalledNodecgVersion() {
|
||||
}
|
||||
|
||||
function assertBundleBuildExists() {
|
||||
for (const entry of requiredBundleEntries) {
|
||||
for (const entry of requiredPreparedBundleEntries) {
|
||||
const source = path.join(bundleRoot, entry);
|
||||
if (!existsSync(source)) {
|
||||
throw new Error(
|
||||
@@ -103,7 +92,7 @@ function createRuntimePackageJson() {
|
||||
};
|
||||
|
||||
writeFileSync(
|
||||
path.join(runtimeRoot, "package.json"),
|
||||
path.join(nodecgRuntimeRoot, "package.json"),
|
||||
`${JSON.stringify(
|
||||
{
|
||||
private: true,
|
||||
@@ -121,23 +110,23 @@ function createRuntimePackageJson() {
|
||||
)}\n`,
|
||||
);
|
||||
|
||||
writeFileSync(path.join(runtimeRoot, "index.js"), 'require("nodecg");\n');
|
||||
writeFileSync(path.join(nodecgRuntimeRoot, "index.js"), 'require("nodecg");\n');
|
||||
}
|
||||
|
||||
function copyBundle() {
|
||||
mkdirSync(runtimeBundleRoot, { recursive: true });
|
||||
|
||||
for (const entry of bundleEntries) {
|
||||
for (const entry of preparedBundleEntries) {
|
||||
copyIfExists(path.join(bundleRoot, entry), path.join(runtimeBundleRoot, entry));
|
||||
}
|
||||
}
|
||||
|
||||
function writeManifest() {
|
||||
const bundlePackageJson = readJson(path.join(bundleRoot, "package.json"));
|
||||
const runtimePackageJson = readJson(path.join(runtimeRoot, "package.json"));
|
||||
const runtimePackageJson = readJson(path.join(nodecgRuntimeRoot, "package.json"));
|
||||
|
||||
writeFileSync(
|
||||
path.join(runtimeRoot, ".scoreko-runtime.json"),
|
||||
path.join(nodecgRuntimeRoot, ".scoreko-runtime.json"),
|
||||
`${JSON.stringify(
|
||||
{
|
||||
bundleName,
|
||||
@@ -157,23 +146,22 @@ function installRuntimeDependencies() {
|
||||
return;
|
||||
}
|
||||
|
||||
const npmCommand = process.platform === "win32" ? "npm.cmd" : "npm";
|
||||
run(npmCommand, ["install", "--omit=dev", "--no-audit", "--no-fund"], runtimeRoot);
|
||||
run(getNpmCommand(), ["install", "--omit=dev", "--no-audit", "--no-fund"], nodecgRuntimeRoot);
|
||||
}
|
||||
|
||||
function main() {
|
||||
assertBundleBuildExists();
|
||||
|
||||
rmSync(runtimeRoot, { recursive: true, force: true });
|
||||
mkdirSync(runtimeNodeModules, { recursive: true });
|
||||
mkdirSync(path.join(runtimeRoot, "bundles"), { recursive: true });
|
||||
rmSync(nodecgRuntimeRoot, { recursive: true, force: true });
|
||||
mkdirSync(nodecgRuntimeNodeModules, { recursive: true });
|
||||
mkdirSync(path.join(nodecgRuntimeRoot, "bundles"), { recursive: true });
|
||||
|
||||
createRuntimePackageJson();
|
||||
copyBundle();
|
||||
installRuntimeDependencies();
|
||||
writeManifest();
|
||||
|
||||
console.log(`[prepare-runtime] NodeCG runtime ready at ${runtimeRoot}`);
|
||||
console.log(`[prepare-runtime] NodeCG runtime ready at ${nodecgRuntimeRoot}`);
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
@@ -2,18 +2,17 @@ import { existsSync, readFileSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { spawn } from "node:child_process";
|
||||
|
||||
const root = process.cwd();
|
||||
const nodecgDir = path.join(root, "lib", "nodecg");
|
||||
const packageJson = JSON.parse(readFileSync(path.join(root, "package.json"), "utf8"));
|
||||
import { electronCache, electronRoot, getNpmCommand, nodecgRuntimeRoot, runtimeNpmCache } from "./build-config.mjs";
|
||||
|
||||
const packageJson = JSON.parse(readFileSync(path.join(electronRoot, "package.json"), "utf8"));
|
||||
const electronVersion = packageJson.devDependencies?.electron ?? packageJson.dependencies?.electron;
|
||||
const npmCommand = process.platform === "win32" ? "npm.cmd" : "npm";
|
||||
|
||||
if (!electronVersion) {
|
||||
console.error("Could not determine Electron version from package.json.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!existsSync(path.join(nodecgDir, "package.json"))) {
|
||||
if (!existsSync(path.join(nodecgRuntimeRoot, "package.json"))) {
|
||||
console.error("No packaged NodeCG runtime found. Run npm run prepare:runtime first.");
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -30,8 +29,8 @@ function run(command, args, cwd) {
|
||||
npm_config_runtime: "electron",
|
||||
npm_config_target: electronVersion,
|
||||
npm_config_disturl: "https://electronjs.org/headers",
|
||||
npm_config_cache: process.env.npm_config_cache ?? path.join(root, ".npm-runtime-cache"),
|
||||
ELECTRON_CACHE: process.env.ELECTRON_CACHE ?? path.join(root, ".electron-cache"),
|
||||
npm_config_cache: runtimeNpmCache,
|
||||
ELECTRON_CACHE: electronCache,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -45,13 +44,13 @@ function run(command, args, cwd) {
|
||||
});
|
||||
}
|
||||
|
||||
console.log(`\n[rebuild-native] Rebuilding better-sqlite3 for Electron ${electronVersion} in: ${nodecgDir}`);
|
||||
await run(npmCommand, [
|
||||
console.log(`\n[rebuild-native] Rebuilding better-sqlite3 for Electron ${electronVersion} in: ${nodecgRuntimeRoot}`);
|
||||
await run(getNpmCommand(), [
|
||||
"rebuild",
|
||||
"better-sqlite3",
|
||||
"--runtime=electron",
|
||||
`--target=${electronVersion}`,
|
||||
"--dist-url=https://electronjs.org/headers",
|
||||
], nodecgDir);
|
||||
], nodecgRuntimeRoot);
|
||||
|
||||
console.log("\n[rebuild-native] Done.");
|
||||
|
||||
Reference in New Issue
Block a user