#!/usr/bin/env node /** * Build peardata-server as a Bare standalone binary (Linux hosts). * * Flow (Holepunch bare-build + bare-node-runtime): * 1. bare-pack the entry with global imports (package.json + bare-node-runtime) * 2. Embed the bundle into a bare-runtime prebuild via bare-build platform hooks * * Usage: * node scripts/bare-standalone.cjs --product server --host linux-x64 * node scripts/bare-standalone.cjs --product server --host all * * Output: * out/peardata-server-/peardata-server */ 'use strict' const path = require('path') const fs = require('fs') const { pathToFileURL } = require('url') const pack = require('bare-pack') const { readModule, listPrefix } = require('bare-pack/fs') const traverse = require('bare-module-traverse') const id = require('bare-bundle-id') const root = path.resolve(__dirname, '..') const pkg = require(path.join(root, 'package.json')) const { SERVER_LINUX } = require('./hosts.cjs') function parseArgs(argv) { const out = { product: 'server', hosts: [], outRoot: path.join(root, 'out'), } for (let i = 0; i < argv.length; i++) { const a = argv[i] if (a === '--product') out.product = argv[++i] else if (a === '--host') { const h = argv[++i] if (h === 'all') out.hosts.push(...SERVER_LINUX) else out.hosts.push(h) } else if (a === '--out') out.outRoot = path.resolve(argv[++i]) else if (a === '--help' || a === '-h') out.help = true } if (!out.hosts.length) { const thisHost = `${process.platform}-${process.arch}` out.hosts.push(SERVER_LINUX.includes(thisHost) ? thisHost : 'linux-x64') } return out } /** * Global imports map for bare-pack. */ function buildImportsMap() { let bnr = {} try { bnr = require('bare-node-runtime/imports') } catch { console.warn( '[bare-standalone] bare-node-runtime/imports not found — relying on package.json imports' ) } return { ...bnr, ...(pkg.imports || {}) } } function platformForHost(host) { const bareBuildRoot = path.dirname(require.resolve('bare-build/package')) const load = (name) => require(path.join(bareBuildRoot, 'lib', 'platform', name)) switch (host) { case 'linux-arm64': case 'linux-x64': return load('linux') default: throw new Error( `peardata-server only builds Linux hosts (got '${host}'). ` + `Allowed: ${SERVER_LINUX.join(', ')}` ) } } /** * @param {string} host * @param {string} outRoot */ async function buildOne(host, outRoot) { if (!SERVER_LINUX.includes(host)) { throw new Error( `Refusing non-Linux server host '${host}'. Allowed: ${SERVER_LINUX.join(', ')}` ) } // Ensure native helpers are compiled + base64-embedded into the JS bundle console.log('[bare-standalone] building/embedding native helpers…') const { spawnSync } = require('child_process') const nh = spawnSync(process.execPath, [path.join(root, 'scripts', 'build-native-helpers.cjs')], { cwd: root, stdio: 'inherit', env: process.env, }) if (nh.status !== 0) { console.warn('[bare-standalone] native helper build failed (JS fallback still works)') } const name = 'peardata-server' const outDir = path.join(outRoot, `${name}-${host}`) fs.rmSync(outDir, { recursive: true, force: true }) fs.mkdirSync(outDir, { recursive: true }) const entryPath = path.join(root, 'bin', 'peardata-server.mjs') if (!fs.existsSync(entryPath)) throw new Error(`Missing entry ${entryPath}`) const imports = buildImportsMap() console.log(`[bare-standalone] packing ${name} for ${host}…`) let entry = await pack( pathToFileURL(entryPath), { hosts: [host], linked: false, resolve: traverse.resolve.bare, imports, }, readModule, listPrefix ) const baseURL = pathToFileURL(root + path.sep) entry = entry.unmount(baseURL) entry.id = id(entry).toString('hex') const platform = platformForHost(host) const opts = { name, version: pkg.version || '0.0.0', description: pkg.description || 'peardata server', author: pkg.author || '', identifier: 'com.peardata.server', hosts: [host], out: outDir, standalone: true, package: false, base: root, } console.log(`[bare-standalone] embedding bare-runtime for ${host}…`) for await (const resource of platform(root, entry, null, opts)) { if (resource && resource.path) { console.log(`[bare-standalone] resource ${resource.path}`) } } const binName = name let binary = path.join(outDir, binName) if (!fs.existsSync(binary)) { const found = walkFind(outDir, (f) => { const base = path.basename(f) return base === name || base === 'peardata-server' }) if (found) binary = found } if (fs.existsSync(binary)) { try { fs.chmodSync(binary, 0o755) } catch { // ignore } const flat = path.join(outDir, path.basename(binary)) if (path.resolve(binary) !== path.resolve(flat)) { fs.copyFileSync(binary, flat) binary = flat } // Also ship raw helper next to binary for ops that prefer an external file const pre = path.join(root, 'native', 'prebuilds', `peardata-ebpf-${host}`) const preAlt = path.join(root, 'native', 'prebuilds', 'peardata-ebpf') const helperSrc = fs.existsSync(pre) ? pre : fs.existsSync(preAlt) ? preAlt : null if (helperSrc) { const helperDest = path.join(outDir, 'peardata-ebpf') fs.copyFileSync(helperSrc, helperDest) try { fs.chmodSync(helperDest, 0o755) } catch { // ignore } console.log(`[bare-standalone] shipped helper ${helperDest}`) } console.log(`[bare-standalone] wrote ${binary}`) } else { console.warn(`[bare-standalone] WARN: expected binary not found under ${outDir}`) console.warn( ' contents:', fs.readdirSync(outDir, { recursive: true }).slice(0, 30).join(', ') ) } fs.writeFileSync( path.join(outDir, 'build-info.json'), JSON.stringify( { product: 'server', host, name, version: pkg.version, builtAt: new Date().toISOString(), entry: 'bin/peardata-server.mjs', bundleId: entry.id, }, null, 2 ) + '\n' ) return outDir } function walkFind(dir, pred) { const stack = [dir] while (stack.length) { const d = stack.pop() let entries try { entries = fs.readdirSync(d, { withFileTypes: true }) } catch { continue } for (const ent of entries) { const p = path.join(d, ent.name) if (ent.isDirectory()) stack.push(p) else if (pred(p)) return p } } return null } async function main() { const opts = parseArgs(process.argv.slice(2)) if (opts.help) { console.log(`Usage: node scripts/bare-standalone.cjs [--product server] [--host |all] [--out dir] Server hosts (Linux only): ${SERVER_LINUX.join(', ')}`) process.exit(0) } if (opts.product !== 'server') { throw new Error( `bare-standalone only builds product=server. For client use: npm run make:client:` ) } const results = [] for (const host of opts.hosts) { results.push(await buildOne(host, opts.outRoot)) } console.log('[bare-standalone] done:', results.join(', ')) } main().catch((err) => { console.error(err) process.exit(1) })