#!/usr/bin/env node const fs = require("fs") const path = require("path") const { execSync } = require("child_process") const { pathToFileURL } = require("url") const root = path.resolve(__dirname, "..") const workspaceRoot = path.resolve(root, "..", "..") const serverRoot = path.resolve(root, "..", "server") const uiRoot = path.resolve(root, "..", "ui") const uiDist = path.resolve(uiRoot, "src", "renderer", "dist") const serverDest = path.resolve(root, "src-tauri", "resources", "server") const uiLoadingDest = path.resolve(root, "src-tauri", "resources", "ui-loading") const resourcesRoot = path.resolve(root, "src-tauri", "resources") const { prepareBundledNodeRuntime } = require(path.join(workspaceRoot, "scripts", "prepare-node-runtime.cjs")) const { copyPackagedServerResources } = require(path.join(workspaceRoot, "scripts", "desktop-server-resources.cjs")) const serverInstallCommand = "npm install --omit=dev --ignore-scripts --workspaces=false --package-lock=false --install-strategy=shallow --fund=false --audit=false" const serverDevInstallCommand = "npm install --workspace @neuralnomads/codenomad --include-workspace-root=false --install-strategy=nested --fund=false --audit=false" const pluginDevInstallCommand = "npm install --workspace @codenomad/codenomad-opencode-plugin --include-workspace-root=false --install-strategy=nested --fund=false --audit=false" const uiDevInstallCommand = "npm install --workspace @codenomad/ui --include-workspace-root=false --install-strategy=nested --fund=false --audit=false" const serverPrepareUiCommand = "npm run prepare-ui --workspace @neuralnomads/codenomad" const envWithRootBin = { ...process.env, PATH: `${path.join(workspaceRoot, "node_modules/.bin")}:${process.env.PATH}`, } const braceExpansionPath = path.join( serverRoot, "node_modules", "@fastify", "static", "node_modules", "brace-expansion", "package.json", ) const serverBuildDependencyPaths = [ path.join(serverRoot, "node_modules", "typescript", "package.json"), path.join(serverRoot, "node_modules", "@types", "node-forge", "package.json"), path.join(serverRoot, "node_modules", "@types", "yauzl", "package.json"), ] const pluginRoot = path.resolve(root, "..", "opencode-plugin") const pluginBuildDependencyPaths = [ path.join(pluginRoot, "node_modules", "typescript", "package.json"), path.join(pluginRoot, "node_modules", "@types", "node", "package.json"), ] const viteBinPath = path.join(uiRoot, "node_modules", ".bin", "vite") async function ensureMonacoAssets() { const helperPath = path.join(uiRoot, "scripts", "monaco-public-assets.js") const helperUrl = pathToFileURL(helperPath).href const { copyMonacoPublicAssets } = await import(helperUrl) copyMonacoPublicAssets({ uiRendererRoot: path.join(uiRoot, "src", "renderer"), warn: (msg) => console.warn(`[prebuild] ${msg}`), sourceRoots: [ path.resolve(workspaceRoot, "node_modules", "monaco-editor", "min", "vs"), path.resolve(uiRoot, "node_modules", "monaco-editor", "min", "vs"), ], }) } function ensureServerBuild() { const distPath = path.join(serverRoot, "dist") const publicPath = path.join(serverRoot, "public") console.log("[prebuild] rebuilding server workspace for desktop packaging...") execSync("npm --workspace @neuralnomads/codenomad run build", { cwd: workspaceRoot, stdio: "inherit", env: { ...process.env, PATH: `${path.join(workspaceRoot, "node_modules/.bin")}:${process.env.PATH}`, }, }) if (!fs.existsSync(distPath) || !fs.existsSync(publicPath)) { throw new Error("[prebuild] server artifacts still missing after build") } } function ensureUiBuild() { const loadingHtml = path.join(uiDist, "loading.html") if (fs.existsSync(loadingHtml)) { return } console.log("[prebuild] ui build missing; running workspace build...") execSync("npm --workspace @codenomad/ui run build", { cwd: workspaceRoot, stdio: "inherit", }) if (!fs.existsSync(loadingHtml)) { throw new Error("[prebuild] ui loading assets missing after build") } } function syncServerUiBundle() { console.log("[prebuild] syncing server public UI bundle...") execSync(serverPrepareUiCommand, { cwd: workspaceRoot, stdio: "inherit", env: envWithRootBin, }) } function ensureServerDevDependencies() { if (serverBuildDependencyPaths.every((filePath) => fs.existsSync(filePath))) { return } console.log("[prebuild] ensuring server build dependencies (with dev)...") execSync(serverDevInstallCommand, { cwd: workspaceRoot, stdio: "inherit", env: envWithRootBin, }) } function ensurePluginDevDependencies() { if (pluginBuildDependencyPaths.every((filePath) => fs.existsSync(filePath))) { return } console.log("[prebuild] ensuring OpenCode plugin build dependencies...") execSync(pluginDevInstallCommand, { cwd: workspaceRoot, stdio: "inherit", env: envWithRootBin, }) } function ensureServerDependencies() { if (fs.existsSync(braceExpansionPath)) { return } console.log("[prebuild] ensuring server production dependencies...") execSync(serverInstallCommand, { cwd: serverRoot, stdio: "inherit", }) } function ensureUiDevDependencies() { if (fs.existsSync(viteBinPath)) { return } console.log("[prebuild] ensuring ui build dependencies...") execSync(uiDevInstallCommand, { cwd: workspaceRoot, stdio: "inherit", env: envWithRootBin, }) } function ensureRollupPlatformBinary() { const platformKey = `${process.platform}-${process.arch}` const platformPackages = { "linux-x64": "@rollup/rollup-linux-x64-gnu", "linux-arm64": "@rollup/rollup-linux-arm64-gnu", "darwin-arm64": "@rollup/rollup-darwin-arm64", "darwin-x64": "@rollup/rollup-darwin-x64", "win32-arm64": "@rollup/rollup-win32-arm64-msvc", "win32-x64": "@rollup/rollup-win32-x64-msvc", } const pkgName = platformPackages[platformKey] if (!pkgName) { return } const platformPackagePath = path.join(workspaceRoot, "node_modules", "@rollup", pkgName.split("/").pop()) if (fs.existsSync(platformPackagePath)) { return } let rollupVersion = "" try { rollupVersion = require(path.join(workspaceRoot, "node_modules", "rollup", "package.json")).version } catch (error) { // leave version empty; fallback install will use latest compatible } const packageSpec = rollupVersion ? `${pkgName}@${rollupVersion}` : pkgName console.log("[prebuild] installing rollup platform binary (optional dep workaround)...") execSync(`npm install ${packageSpec} --no-save --ignore-scripts --fund=false --audit=false`, { cwd: workspaceRoot, stdio: "inherit", }) } function ensureEsbuildPlatformBinary() { const platformKey = `${process.platform}-${process.arch}` const platformPackages = { "linux-arm": "@esbuild/linux-arm", "linux-arm64": "@esbuild/linux-arm64", "linux-ia32": "@esbuild/linux-ia32", "linux-x64": "@esbuild/linux-x64", "darwin-arm64": "@esbuild/darwin-arm64", "darwin-x64": "@esbuild/darwin-x64", "win32-arm64": "@esbuild/win32-arm64", "win32-ia32": "@esbuild/win32-ia32", "win32-x64": "@esbuild/win32-x64", } const pkgName = platformPackages[platformKey] if (!pkgName) { return } const platformPackageName = pkgName.split("/").pop() const platformPackagePaths = [ path.join(serverRoot, "node_modules", "@esbuild", platformPackageName), path.join(workspaceRoot, "node_modules", "@esbuild", platformPackageName), ] if (platformPackagePaths.some((packagePath) => fs.existsSync(packagePath))) { return } let esbuildVersion = "" for (const baseRoot of [serverRoot, workspaceRoot]) { try { esbuildVersion = require(path.join(baseRoot, "node_modules", "esbuild", "package.json")).version break } catch (error) { // try the next install root; fallback install will use latest compatible } } const packageSpec = esbuildVersion ? `${pkgName}@${esbuildVersion}` : pkgName console.log("[prebuild] installing esbuild platform binary (optional dep workaround)...") execSync(`npm install ${packageSpec} --no-save --ignore-scripts --package-lock=false --fund=false --audit=false`, { cwd: workspaceRoot, stdio: "inherit", }) } function copyUiLoadingAssets() { const loadingSource = path.join(uiDist, "loading.html") const assetsSource = path.join(uiDist, "assets") if (!fs.existsSync(loadingSource)) { throw new Error("[prebuild] cannot find built loading.html") } fs.rmSync(uiLoadingDest, { recursive: true, force: true }) fs.mkdirSync(uiLoadingDest, { recursive: true }) fs.copyFileSync(loadingSource, path.join(uiLoadingDest, "loading.html")) if (fs.existsSync(assetsSource)) { fs.cpSync(assetsSource, path.join(uiLoadingDest, "assets"), { recursive: true }) } console.log(`[prebuild] prepared UI loading assets from ${uiDist}`) } ;(async () => { ensureServerDevDependencies() ensurePluginDevDependencies() ensureUiDevDependencies() await ensureMonacoAssets() ensureRollupPlatformBinary() ensureEsbuildPlatformBinary() ensureServerBuild() ensureServerDependencies() ensureUiBuild() syncServerUiBundle() copyPackagedServerResources({ serverRoot, serverDest, log: (message) => console.log(`[prebuild] ${message}`), }) copyUiLoadingAssets() await prepareBundledNodeRuntime({ resourcesRoot }) execSync( `${JSON.stringify(process.execPath)} ${JSON.stringify(path.join(workspaceRoot, "scripts", "smoke-packaged-resources.cjs"))} --resources ${JSON.stringify(resourcesRoot)} --loading ${JSON.stringify(uiLoadingDest)}`, { cwd: workspaceRoot, stdio: "inherit", }, ) })().catch((err) => { console.error("[prebuild] failed:", err) process.exit(1) })