import { cpSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs' import path from 'node:path' import { fileURLToPath } from 'node:url' const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..') const electrobunDir = path.join(rootDir, 'node_modules/electrobun') const sharedSrcDir = path.join(electrobunDir, 'src/shared') const sharedDistDir = path.join(electrobunDir, 'dist/api/shared') if (!existsSync(sharedDistDir)) { console.error('patch-electrobun-src: electrobun dist/api/shared not found') process.exit(1) } mkdirSync(sharedSrcDir, { recursive: true }) for (const fileName of [ 'cef-version.ts', 'bun-version.ts', 'electrobun-version.ts', 'naming.ts', 'rpc.ts' ]) { const source = path.join(sharedDistDir, fileName) const destination = path.join(sharedSrcDir, fileName) if (existsSync(source)) { cpSync(source, destination) } } const platformPatch = `import { platform, arch } from "os"; export type SupportedOS = "macos" | "win" | "linux"; export type SupportedArch = "arm64" | "x64"; const platformName = platform(); const archName = arch(); export const OS: SupportedOS = (() => { switch (platformName) { case "win32": return "win"; case "darwin": return "macos"; case "linux": return "linux"; default: throw new Error(\`Unsupported platform: \${platformName}\`); } })(); function normalizeForcedArch(value) { if (value === "arm64" || value === "aarch64") { return "arm64"; } if (value === "x64" || value === "amd64") { return "x64"; } return null; } export const ARCH: SupportedArch = (() => { const forcedArch = normalizeForcedArch( process.env.ELECTROBUN_FORCE_ARCH || process.env.ELECTROBUN_TARGET_ARCH, ); if (forcedArch) { return forcedArch; } if (OS === "win") { return "x64"; } switch (archName) { case "arm64": return "arm64"; case "x64": return "x64"; default: throw new Error(\`Unsupported architecture: \${archName}\`); } })(); export const HOST_ARCH: SupportedArch = (() => { if (OS === "win") { return "x64"; } switch (archName) { case "arm64": return "arm64"; case "x64": return "x64"; default: throw new Error(\`Unsupported architecture: \${archName}\`); } })(); export function getPlatformOS(): SupportedOS { return OS; } export function getPlatformArch(): SupportedArch { return ARCH; } ` writeFileSync(path.join(sharedSrcDir, 'platform.ts'), platformPatch) const templatesDir = path.join(electrobunDir, 'src/cli/templates') mkdirSync(templatesDir, { recursive: true }) writeFileSync( path.join(templatesDir, 'embedded.ts'), `export function getTemplateNames() { return []; } export function getTemplate(name: string) { throw new Error(\`Template not available in patched electrobun CLI: \${name}\`); } ` ) const RCEDIT_RESOLVER_FN = `function resolveProjectRceditPkgPath(projectRoot) { const candidates = [ join(projectRoot, "node_modules/rcedit/package.json"), join(projectRoot, "node_modules/electrobun/node_modules/rcedit/package.json"), ]; for (const candidate of candidates) { if (existsSync(candidate)) { return candidate; } } throw new Error( "rcedit not found under " + projectRoot + ". Install rcedit in the project (bun add -d rcedit).", ); }` const cliPath = path.join(electrobunDir, 'src/cli/index.ts') let cliSource = readFileSync(cliPath, 'utf8') if (!cliSource.includes('HOST_ARCH')) { cliSource = cliSource.replace( 'import { OS, ARCH } from "../shared/platform";', 'import { OS, ARCH, HOST_ARCH } from "../shared/platform";' ) cliSource = cliSource.replace( 'const hostPaths = getPlatformPaths(OS, ARCH);', 'const hostPaths = getPlatformPaths(OS, HOST_ARCH);' ) } if (!cliSource.includes('toolPaths')) { cliSource = cliSource.replace( 'const targetPaths = getPlatformPaths(currentTarget.os, currentTarget.arch);', 'const targetPaths = getPlatformPaths(currentTarget.os, currentTarget.arch);\n\t\tconst toolPaths = getPlatformPaths(currentTarget.os, ARCH !== HOST_ARCH ? HOST_ARCH : ARCH);' ) cliSource = cliSource.replaceAll( 'const zstdPath = targetPaths.ZSTD;', 'const zstdPath = toolPaths.ZSTD;' ) cliSource = cliSource.replaceAll( 'const bsdiffpath = targetPaths.BSDIFF;', 'const bsdiffpath = toolPaths.BSDIFF;' ) cliSource = cliSource.replace( 'zigAsarCli = join(targetPaths.BSPATCH).replace("bspatch", "zig-asar");', 'zigAsarCli = join(toolPaths.BSPATCH).replace("bspatch", "zig-asar");' ) writeFileSync(cliPath, cliSource) cliSource = readFileSync(cliPath, 'utf8') } if (!cliSource.includes('hookBunBinary')) { cliSource = cliSource.replace( `const hostPaths = getPlatformPaths(OS, HOST_ARCH); const result = Bun.spawnSync([hostPaths.BUN_BINARY, hookScript],`, `const hostPaths = getPlatformPaths(OS, HOST_ARCH); const hookBunBinary = existsSync(hostPaths.BUN_BINARY) ? hostPaths.BUN_BINARY : process.execPath; const result = Bun.spawnSync([hookBunBinary, hookScript],` ) cliSource = cliSource.replace( 'console.error("Tried to run with bun at:", hostPaths.BUN_BINARY);', 'console.error("Tried to run with bun at:", hookBunBinary);' ) writeFileSync(cliPath, cliSource) cliSource = readFileSync(cliPath, 'utf8') } if (!cliSource.includes('resolveProjectRceditPkgPath')) { cliSource = cliSource.replaceAll( 'const rceditPkgPath = require.resolve("rcedit/package.json");', 'const rceditPkgPath = resolveProjectRceditPkgPath(projectRoot);' ) cliSource = cliSource.replace( 'function getPlatformPaths(', `${RCEDIT_RESOLVER_FN} function getPlatformPaths(` ) writeFileSync(cliPath, cliSource) cliSource = readFileSync(cliPath, 'utf8') } else { cliSource = cliSource.replace( /function resolveProjectRceditPkgPath\(projectRoot\) \{[\s\S]*?\n\}/m, RCEDIT_RESOLVER_FN ) writeFileSync(cliPath, cliSource) } const electrobunCjsPath = path.join(electrobunDir, 'bin/electrobun.cjs') let electrobunCjs = readFileSync(electrobunCjsPath, 'utf8') if (!electrobunCjs.includes('farmcontrol-use-patched-cli')) { electrobunCjs = electrobunCjs.replace( `async function main() { try { const args = process.argv.slice(2); const cliPath = await ensureCliBinary(); // Replace this process with the actual CLI const child = spawn(cliPath, args, { stdio: 'inherit', cwd: process.cwd() });`, `async function main() { try { const args = process.argv.slice(2); const patchedCliPath = join(electrobunDir, 'src', 'cli', 'index.ts'); if (existsSync(patchedCliPath)) { // farmcontrol-use-patched-cli: bundled electrobun.exe cannot resolve project rcedit const bunBinary = process.env.BUN_INSTALL ? join(process.env.BUN_INSTALL, 'bin', 'bun' + binExt) : 'bun'; const child = spawn(bunBinary, [patchedCliPath, ...args], { stdio: 'inherit', cwd: process.cwd(), env: process.env, shell: platform === 'win', }); child.on('exit', (code) => { process.exit(code || 0); }); child.on('error', (error) => { console.error('Failed to start electrobun patched CLI:', error.message); process.exit(1); }); return; } const cliPath = await ensureCliBinary(); // Replace this process with the actual CLI const child = spawn(cliPath, args, { stdio: 'inherit', cwd: process.cwd() });` ) writeFileSync(electrobunCjsPath, electrobunCjs) } const rceditSrc = path.join(rootDir, 'node_modules/rcedit') const rceditDest = path.join(electrobunDir, 'node_modules/rcedit') if (existsSync(rceditSrc)) { mkdirSync(path.join(electrobunDir, 'node_modules'), { recursive: true }) cpSync(rceditSrc, rceditDest, { recursive: true, force: true }) } const markerPath = path.join(electrobunDir, '.farmcontrol-electrobun-patched') writeFileSync(markerPath, `patched-at=${new Date().toISOString()}\n`) console.log( 'patch-electrobun-src: electrobun src/shared ready for cross-arch builds' )