fix(desktop): restore managed Node server startup (#348)
## Summary - revert the Bun standalone desktop packaging path and restore the server's original `dist/bin.js` bootstrap flow - add a managed Node runtime for Electron and Tauri that downloads only the current platform/arch artifact into `~/.config/codenomad` - update desktop startup and packaging scripts so packaged apps use the managed runtime consistently, and clean up Electron's expected navigation-abort log noise ## Testing - npm run typecheck --workspace @neuralnomads/codenomad-electron-app - cargo check - npm run build --workspace @neuralnomads/codenomad - npm run build:mac --workspace @neuralnomads/codenomad-electron-app - launch `packages/electron-app/release/mac-arm64/CodeNomad.app/Contents/MacOS/CodeNomad` and verify the packaged server reaches ready with the managed Node runtime
This commit is contained in:
@@ -1,99 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
import fs from "fs"
|
||||
import path from "path"
|
||||
import { spawnSync } from "child_process"
|
||||
import { fileURLToPath } from "url"
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url)
|
||||
const __dirname = path.dirname(__filename)
|
||||
const cliRoot = path.resolve(__dirname, "..")
|
||||
const distDir = path.join(cliRoot, "dist")
|
||||
const publicDir = path.join(cliRoot, "public")
|
||||
const authPagesSourceDir = path.join(distDir, "server", "routes", "auth-pages")
|
||||
const authPagesTargetDir = path.join(distDir, "auth-pages")
|
||||
const explicitTarget = process.env.CODENOMAD_STANDALONE_TARGET?.trim()
|
||||
const outputName = (explicitTarget?.includes("windows") || process.platform === "win32") ? "codenomad-server.exe" : "codenomad-server"
|
||||
const outputPath = path.join(distDir, outputName)
|
||||
const packageJsonPath = path.join(cliRoot, "package.json")
|
||||
|
||||
function resolveBunCommand() {
|
||||
const executableName = process.platform === "win32" ? "bun.exe" : "bun"
|
||||
const localBinName = process.platform === "win32" ? "bun.cmd" : "bun"
|
||||
const candidates = [
|
||||
path.join(cliRoot, "node_modules", ".bin", localBinName),
|
||||
path.join(cliRoot, "..", "..", "node_modules", ".bin", localBinName),
|
||||
path.join(cliRoot, "node_modules", "bun", "bin", executableName),
|
||||
path.join(cliRoot, "..", "..", "node_modules", "bun", "bin", executableName),
|
||||
]
|
||||
|
||||
for (const candidate of candidates) {
|
||||
if (fs.existsSync(candidate)) {
|
||||
return candidate
|
||||
}
|
||||
}
|
||||
|
||||
return "bun"
|
||||
}
|
||||
|
||||
function fail(message) {
|
||||
console.error(`[build-standalone] ${message}`)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
function ensureArtifacts() {
|
||||
const requiredPaths = [distDir, publicDir, authPagesSourceDir, packageJsonPath]
|
||||
const missing = requiredPaths.filter((filePath) => !fs.existsSync(filePath))
|
||||
if (missing.length > 0) {
|
||||
fail(`Missing required build artifacts: ${missing.join(", ")}. Run npm run build first.`)
|
||||
}
|
||||
|
||||
const bunResult = spawnSync(resolveBunCommand(), ["-v"], { cwd: cliRoot, encoding: "utf-8", shell: process.platform === "win32" })
|
||||
if (bunResult.status !== 0) {
|
||||
fail("Bun is required to build the standalone server executable. Install dependencies so the local Bun binary is available.")
|
||||
}
|
||||
}
|
||||
|
||||
function syncStandaloneAuthPages() {
|
||||
fs.rmSync(authPagesTargetDir, { recursive: true, force: true })
|
||||
fs.mkdirSync(path.dirname(authPagesTargetDir), { recursive: true })
|
||||
fs.cpSync(authPagesSourceDir, authPagesTargetDir, { recursive: true })
|
||||
}
|
||||
|
||||
function buildStandaloneExecutable() {
|
||||
fs.rmSync(outputPath, { force: true })
|
||||
const bunCommand = resolveBunCommand()
|
||||
|
||||
const args = ["build", "--compile"]
|
||||
if (explicitTarget) {
|
||||
args.push(`--target=${explicitTarget}`)
|
||||
}
|
||||
args.push(path.join(cliRoot, "src", "index.ts"), "--outfile", outputPath)
|
||||
|
||||
const result = spawnSync(bunCommand, args, {
|
||||
cwd: cliRoot,
|
||||
stdio: "inherit",
|
||||
shell: process.platform === "win32",
|
||||
})
|
||||
|
||||
if (result.status !== 0) {
|
||||
if (result.error) {
|
||||
throw result.error
|
||||
}
|
||||
throw new Error(`bun build --compile exited with code ${result.status ?? 1}`)
|
||||
}
|
||||
}
|
||||
|
||||
function main() {
|
||||
ensureArtifacts()
|
||||
syncStandaloneAuthPages()
|
||||
|
||||
buildStandaloneExecutable()
|
||||
console.log(`[build-standalone] built ${outputPath}`)
|
||||
}
|
||||
|
||||
try {
|
||||
main()
|
||||
} catch (error) {
|
||||
console.error("[build-standalone] failed:", error)
|
||||
process.exit(1)
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
import { spawnSync } from "child_process"
|
||||
import { cpSync, existsSync, mkdirSync, readdirSync, rmSync } from "fs"
|
||||
import { cpSync, existsSync, mkdirSync, rmSync } from "fs"
|
||||
import path from "path"
|
||||
import { fileURLToPath } from "url"
|
||||
|
||||
@@ -14,67 +14,6 @@ const selfLinkDir = path.resolve(nodeModulesDir, "@codenomad", "opencode-config"
|
||||
const npmExecPath = process.env.npm_execpath
|
||||
const npmNodeExecPath = process.env.npm_node_execpath
|
||||
|
||||
function stripNodeModuleBins(rootDir) {
|
||||
const root = path.join(rootDir, "node_modules")
|
||||
if (!existsSync(root)) {
|
||||
return 0
|
||||
}
|
||||
|
||||
const stack = [root]
|
||||
let removed = 0
|
||||
|
||||
while (stack.length > 0) {
|
||||
const current = stack.pop()
|
||||
if (!current) break
|
||||
|
||||
let entries
|
||||
try {
|
||||
entries = readdirSync(current, { withFileTypes: true })
|
||||
} catch {
|
||||
continue
|
||||
}
|
||||
|
||||
for (const entry of entries) {
|
||||
const full = path.join(current, entry.name)
|
||||
if (entry.name === ".bin") {
|
||||
rmSync(full, { recursive: true, force: true })
|
||||
removed += 1
|
||||
continue
|
||||
}
|
||||
|
||||
if (entry.isDirectory()) {
|
||||
stack.push(full)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return removed
|
||||
}
|
||||
|
||||
function stripOptionalNativeAddons(rootDir) {
|
||||
const nodeModulesRoot = path.join(rootDir, "node_modules")
|
||||
if (!existsSync(nodeModulesRoot)) {
|
||||
return 0
|
||||
}
|
||||
|
||||
const removablePaths = [
|
||||
path.join(nodeModulesRoot, "@msgpackr-extract"),
|
||||
path.join(nodeModulesRoot, "msgpackr-extract"),
|
||||
]
|
||||
|
||||
let removed = 0
|
||||
for (const targetPath of removablePaths) {
|
||||
if (!existsSync(targetPath)) {
|
||||
continue
|
||||
}
|
||||
|
||||
rmSync(targetPath, { recursive: true, force: true })
|
||||
removed += 1
|
||||
}
|
||||
|
||||
return removed
|
||||
}
|
||||
|
||||
if (!existsSync(sourceDir)) {
|
||||
console.error(`[copy-opencode-config] Missing source directory at ${sourceDir}`)
|
||||
process.exit(1)
|
||||
@@ -119,14 +58,4 @@ rmSync(targetDir, { recursive: true, force: true })
|
||||
mkdirSync(path.dirname(targetDir), { recursive: true })
|
||||
cpSync(sourceDir, targetDir, { recursive: true })
|
||||
|
||||
const removedBins = stripNodeModuleBins(targetDir)
|
||||
if (removedBins > 0) {
|
||||
console.log(`[copy-opencode-config] Removed ${removedBins} node_modules/.bin directories`)
|
||||
}
|
||||
|
||||
const removedNativeAddons = stripOptionalNativeAddons(targetDir)
|
||||
if (removedNativeAddons > 0) {
|
||||
console.log(`[copy-opencode-config] Removed ${removedNativeAddons} optional native addon package paths`)
|
||||
}
|
||||
|
||||
console.log(`[copy-opencode-config] Copied ${sourceDir} -> ${targetDir}`)
|
||||
|
||||
Reference in New Issue
Block a user