#!/usr/bin/env node import { spawn } from "child_process" import { existsSync, readFileSync } from "fs" import path, { join } from "path" import { fileURLToPath } from "url" const __dirname = fileURLToPath(new URL(".", import.meta.url)) const appDir = join(__dirname, "..") const workspaceRoot = join(appDir, "..", "..") const npmCmd = process.platform === "win32" ? "npm.cmd" : "npm" const npxCmd = process.platform === "win32" ? "npx.cmd" : "npx" const nodeModulesPath = join(appDir, "node_modules") const workspaceNodeModulesPath = join(workspaceRoot, "node_modules") function getPlatformEsbuildPackage() { const platformKey = `${process.platform}-${process.arch}` const platformPackages = { "linux-x64": "@esbuild/linux-x64", "linux-arm64": "@esbuild/linux-arm64", "darwin-arm64": "@esbuild/darwin-arm64", "darwin-x64": "@esbuild/darwin-x64", "win32-arm64": "@esbuild/win32-arm64", "win32-x64": "@esbuild/win32-x64", } return platformPackages[platformKey] ?? null } async function ensureEsbuildPlatformBinary() { const pkgName = getPlatformEsbuildPackage() if (!pkgName) { return } const platformPackagePath = join(workspaceNodeModulesPath, ...pkgName.split("/")) if (existsSync(platformPackagePath)) { return } let esbuildVersion = "" try { esbuildVersion = JSON.parse(readFileSync(join(workspaceNodeModulesPath, "esbuild", "package.json"), "utf-8")).version ?? "" } catch { // leave version empty; fallback install will use latest compatible } const packageSpec = esbuildVersion ? `${pkgName}@${esbuildVersion}` : pkgName console.log("šŸ“¦ Step 0/3: Restoring esbuild platform binary...\n") await run(npmCmd, ["install", packageSpec, "--no-save", "--ignore-scripts", "--fund=false", "--audit=false"], { cwd: workspaceRoot, env: { NODE_PATH: workspaceNodeModulesPath }, }) } const platforms = { mac: { args: ["--mac", "--x64", "--arm64"], description: "macOS (Intel & Apple Silicon)", }, "mac-x64": { args: ["--mac", "--x64"], description: "macOS (Intel only)", }, "mac-arm64": { args: ["--mac", "--arm64"], description: "macOS (Apple Silicon only)", }, win: { args: ["--win", "--x64"], description: "Windows (x64)", }, "win-arm64": { args: ["--win", "--arm64"], description: "Windows (ARM64)", }, linux: { args: ["--linux", "--x64"], description: "Linux (x64)", }, "linux-arm64": { args: ["--linux", "--arm64"], description: "Linux (ARM64)", }, "linux-rpm": { args: ["--linux", "rpm", "--x64", "--arm64"], description: "Linux RPM packages (x64 & ARM64)", }, all: { args: ["--mac", "--win", "--linux", "--x64", "--arm64"], description: "All platforms (macOS, Windows, Linux)", }, } function run(command, args, options = {}) { return new Promise((resolve, reject) => { const env = { ...process.env, NODE_PATH: nodeModulesPath, ...(options.env || {}) } const pathKey = Object.keys(env).find((key) => key.toLowerCase() === "path") ?? "PATH" const binPaths = [ join(nodeModulesPath, ".bin"), join(workspaceNodeModulesPath, ".bin"), ] env[pathKey] = `${binPaths.join(path.delimiter)}${path.delimiter}${env[pathKey] ?? ""}` const spawnOptions = { cwd: appDir, stdio: "inherit", shell: process.platform === "win32", ...options, env, } const child = spawn(command, args, spawnOptions) child.on("error", reject) child.on("exit", (code) => { if (code === 0) { resolve(undefined) } else { reject(new Error(`${command} ${args.join(" ")} exited with code ${code}`)) } }) }) } function printAvailablePlatforms() { console.error(`\nAvailable platforms:`) for (const [name, cfg] of Object.entries(platforms)) { console.error(` - ${name.padEnd(12)} : ${cfg.description}`) } } async function build(platform) { const config = platforms[platform] if (!config) { console.error(`āŒ Unknown platform: ${platform}`) printAvailablePlatforms() process.exit(1) } console.log(`\nšŸ”Ø Building for: ${config.description}\n`) try { await ensureEsbuildPlatformBinary() console.log("šŸ“¦ Step 1/3: Building CLI dependency...\n") await run(npmCmd, ["run", "build", "--workspace", "@neuralnomads/codenomad"], { cwd: workspaceRoot, env: { NODE_PATH: workspaceNodeModulesPath }, }) console.log("\nšŸ“¦ Step 1.5/3: Preparing packaged server resources...\n") await run(process.execPath, [join(appDir, "scripts", "prepare-resources.js")], { cwd: workspaceRoot, env: { NODE_PATH: workspaceNodeModulesPath }, }) console.log("\nšŸ“¦ Step 2/3: Building Electron app...\n") await run(npmCmd, ["run", "build"]) console.log("\nšŸ“¦ Step 3/3: Packaging binaries...\n") const distPath = join(appDir, "dist") if (!existsSync(distPath)) { throw new Error("dist/ directory not found. Build failed.") } await run(npxCmd, ["electron-builder", "--publish=never", ...config.args]) console.log("\nāœ… Build complete!") console.log(`šŸ“ Binaries available in: ${join(appDir, "release")}\n`) } catch (error) { console.error("\nāŒ Build failed:", error) process.exit(1) } } const platform = process.argv[2] || "mac" console.log(` ╔════════════════════════════════════════╗ ā•‘ CodeNomad - Binary Builder ā•‘ ā•šā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā• `) await build(platform)