Files
peardata/scripts/bare-standalone.cjs
T
Raven Scott a639b3c953
CI / test (push) Successful in 1m15s
Release rolling / release (push) Successful in 7m6s
Updates
2026-07-18 19:49:57 -04:00

262 lines
7.2 KiB
JavaScript

#!/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-<host>/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 <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:<host>`
)
}
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)
})