#!/usr/bin/env node import { cpSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"; 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", "schemas", "shared", "configschema.json", "LICENSE", "package.json", "README.md", ]; const requiredBundleEntries = ["dashboard", "extension", "graphics", "schemas", "shared", "package.json"]; function readJson(filePath) { return JSON.parse(readFileSync(filePath, "utf8")); } function copyIfExists(source, destination) { if (!existsSync(source)) { return false; } cpSync(source, destination, { recursive: true, force: true, dereference: true, filter: (sourcePath) => !sourcePath.split(path.sep).includes("node_modules"), }); return true; } function run(command, args, cwd) { const result = spawnSync(command, args, { cwd, stdio: "inherit", shell: process.platform === "win32", env: { ...process.env, npm_config_cache: process.env.npm_config_cache ?? path.join(electronRoot, ".npm-runtime-cache"), }, }); if (result.error) { throw new Error(`${command} ${args.join(" ")} failed: ${result.error.message}`); } if (result.status !== 0) { throw new Error(`${command} ${args.join(" ")} failed with code ${result.status}`); } } function getInstalledNodecgVersion() { const nodecgPackagePath = path.join(bundleRoot, "node_modules", "nodecg", "package.json"); if (!existsSync(nodecgPackagePath)) { throw new Error( [ "NodeCG is not installed in the parent project.", `Expected: ${nodecgPackagePath}`, `Run 'pnpm install' from ${bundleRoot} before packaging.`, ].join("\n"), ); } return readJson(nodecgPackagePath).version; } function assertBundleBuildExists() { for (const entry of requiredBundleEntries) { const source = path.join(bundleRoot, entry); if (!existsSync(source)) { throw new Error( [ `The built Scoreko bundle is missing '${entry}'.`, `Expected: ${source}`, `Run 'pnpm build' from ${bundleRoot} before packaging.`, ].join("\n"), ); } } } function createRuntimePackageJson() { const bundlePackageJson = readJson(path.join(bundleRoot, "package.json")); const dependencies = { nodecg: getInstalledNodecgVersion(), ...(bundlePackageJson.dependencies ?? {}), }; writeFileSync( path.join(runtimeRoot, "package.json"), `${JSON.stringify( { private: true, name: "scoreko-nodecg-runtime", version: bundlePackageJson.version ?? "0.0.0", description: "Packaged NodeCG runtime for Scoreko Desktop.", type: "commonjs", scripts: { start: "node index.js", }, dependencies, }, null, 2, )}\n`, ); writeFileSync(path.join(runtimeRoot, "index.js"), 'require("nodecg");\n'); } function copyBundle() { mkdirSync(runtimeBundleRoot, { recursive: true }); for (const entry of bundleEntries) { 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")); writeFileSync( path.join(runtimeRoot, ".scoreko-runtime.json"), `${JSON.stringify( { bundleName, bundleVersion: bundlePackageJson.version ?? "0.0.0", nodecgVersion: runtimePackageJson.dependencies.nodecg, generatedAt: new Date().toISOString(), }, null, 2, )}\n`, ); } function installRuntimeDependencies() { if (process.env.SCOREKO_SKIP_RUNTIME_NPM_INSTALL === "1") { console.log("[prepare-runtime] Skipping runtime npm install by environment request."); return; } const npmCommand = process.platform === "win32" ? "npm.cmd" : "npm"; run(npmCommand, ["install", "--omit=dev", "--no-audit", "--no-fund"], runtimeRoot); } function main() { assertBundleBuildExists(); rmSync(runtimeRoot, { recursive: true, force: true }); mkdirSync(runtimeNodeModules, { recursive: true }); mkdirSync(path.join(runtimeRoot, "bundles"), { recursive: true }); createRuntimePackageJson(); copyBundle(); installRuntimeDependencies(); writeManifest(); console.log(`[prepare-runtime] NodeCG runtime ready at ${runtimeRoot}`); } try { main(); } catch (error) { console.error(error instanceof Error ? error.message : error); process.exit(1); }