291 lines
8.3 KiB
JavaScript
291 lines
8.3 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Pack Flying Jib as a Bare standalone executable (peardock pattern).
|
|
*
|
|
* bare-build CLI does not accept --imports; this script uses bare-pack with
|
|
* build/squid-imports.json + package.json imports, then embeds into bare-runtime.
|
|
*
|
|
* Usage:
|
|
* node scripts/bare-standalone.cjs
|
|
* node scripts/bare-standalone.cjs --host darwin-arm64
|
|
* node scripts/bare-standalone.cjs --host all
|
|
*/
|
|
'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 ALL_HOSTS = [
|
|
'darwin-arm64',
|
|
'darwin-x64',
|
|
'linux-arm64',
|
|
'linux-x64',
|
|
'win32-arm64',
|
|
'win32-x64'
|
|
]
|
|
|
|
function parseArgs(argv) {
|
|
const out = { hosts: [], outRoot: path.join(root, 'out') }
|
|
for (let i = 0; i < argv.length; i++) {
|
|
const a = argv[i]
|
|
if (a === '--host') {
|
|
const h = argv[++i]
|
|
if (h === 'all') out.hosts.push(...ALL_HOSTS)
|
|
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) out.hosts.push(`${process.platform}-${process.arch}`)
|
|
return out
|
|
}
|
|
|
|
function fileURL(rel) {
|
|
return pathToFileURL(path.join(root, rel)).href
|
|
}
|
|
|
|
function buildImportsMap() {
|
|
// Prefer generated squid-imports (bnr + shims)
|
|
const squidImportsPath = path.join(root, 'build', 'squid-imports.json')
|
|
let map = {}
|
|
if (fs.existsSync(squidImportsPath)) {
|
|
map = JSON.parse(fs.readFileSync(squidImportsPath, 'utf8'))
|
|
} else {
|
|
try {
|
|
map = { ...require('bare-node-runtime/imports') }
|
|
} catch {
|
|
map = {}
|
|
}
|
|
}
|
|
|
|
// Merge package.json imports (relative bare paths need absolutizing)
|
|
const pkgImports = pkg.imports || {}
|
|
for (const [spec, target] of Object.entries(pkgImports)) {
|
|
if (typeof target === 'string') {
|
|
map[spec] = target
|
|
continue
|
|
}
|
|
if (target && typeof target === 'object') {
|
|
map[spec] = { ...target }
|
|
}
|
|
}
|
|
|
|
for (const [spec, target] of Object.entries(map)) {
|
|
if (!target || typeof target !== 'object') continue
|
|
if (typeof target.bare === 'string' && target.bare.startsWith('./')) {
|
|
map[spec] = {
|
|
...target,
|
|
bare: fileURL(target.bare.replace(/^\.\//, ''))
|
|
}
|
|
}
|
|
}
|
|
|
|
// Explicit stubs for optional / browser-only / missing optional deps
|
|
const stubs = {
|
|
'esbuild-import-glob(path:.,skipFiles:index.js,external.js)':
|
|
'build/stubs/esbuild-import-glob.cjs',
|
|
'supports-color': 'build/stubs/supports-color.cjs',
|
|
'cpu-features': 'build/stubs/supports-color.cjs',
|
|
'encoding': 'build/stubs/auto-encoding.cjs',
|
|
'bufferutil': 'build/stubs/auto-bufferutil.cjs',
|
|
'utf-8-validate': 'build/stubs/auto-utf-8-validate.cjs',
|
|
'canvas': 'build/stubs/auto-canvas.cjs',
|
|
'sqlite3': 'build/stubs/auto-sqlite3.cjs',
|
|
'better-sqlite3': 'build/stubs/auto-better-sqlite3.cjs'
|
|
}
|
|
for (const [spec, rel] of Object.entries(stubs)) {
|
|
map[spec] = { bare: fileURL(rel), default: fileURL(rel) }
|
|
}
|
|
|
|
return map
|
|
}
|
|
|
|
function platformForHost(host) {
|
|
const bareBuildRoot = path.dirname(require.resolve('bare-build/package'))
|
|
const load = (name) => require(path.join(bareBuildRoot, 'lib', 'platform', name))
|
|
if (host.startsWith('darwin') || host.startsWith('ios')) return load('apple')
|
|
if (host.startsWith('linux')) return load('linux')
|
|
if (host.startsWith('win32')) return load('windows')
|
|
if (host.startsWith('android')) return load('android')
|
|
throw new Error(`Unknown host '${host}'`)
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
function runNodeScript(rel, extraArgs = []) {
|
|
require('child_process').execSync(
|
|
`node ${JSON.stringify(path.join(root, rel))}${extraArgs.map((a) => ' ' + JSON.stringify(a)).join('')}`,
|
|
{ cwd: root, stdio: 'inherit' }
|
|
)
|
|
}
|
|
|
|
async function buildOne(host, outRoot) {
|
|
const name = pkg.productName ? 'flying-jib' : pkg.name
|
|
const outDir = path.join(outRoot, `${name}-${host}`)
|
|
fs.rmSync(outDir, { recursive: true, force: true })
|
|
fs.mkdirSync(outDir, { recursive: true })
|
|
|
|
// Ensure postinstall artifacts
|
|
runNodeScript('scripts/generate-squid-imports.js')
|
|
runNodeScript('scripts/patch-engines-for-bare.js')
|
|
|
|
// Shrink pack graph: PC Squid versions only, no bedrock (~hundreds of MiB)
|
|
const skipPrune = process.env.FJ_SKIP_MC_DATA_PRUNE === '1'
|
|
if (!skipPrune) {
|
|
console.log('[bare-standalone] pruning minecraft-data for pack…')
|
|
runNodeScript('scripts/prune-minecraft-data.js')
|
|
} else {
|
|
console.log('[bare-standalone] FJ_SKIP_MC_DATA_PRUNE=1 — packing full minecraft-data')
|
|
}
|
|
|
|
let entry
|
|
try {
|
|
const entryPath = path.join(root, 'bin.mjs')
|
|
const imports = buildImportsMap()
|
|
console.log(`[bare-standalone] packing ${name} for ${host}…`)
|
|
|
|
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 || 'Flying Jib',
|
|
author: pkg.author || '',
|
|
identifier: 'dev.flyingjib.app',
|
|
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}`)
|
|
}
|
|
}
|
|
} finally {
|
|
// Always restore full data.js so local bare/npm start keep multi-version support
|
|
if (!skipPrune) {
|
|
try {
|
|
runNodeScript('scripts/prune-minecraft-data.js', ['--restore'])
|
|
} catch (e) {
|
|
console.warn('[bare-standalone] WARN: could not restore minecraft-data:', e.message)
|
|
}
|
|
}
|
|
}
|
|
|
|
const binName = host.startsWith('win32') ? `${name}.exe` : 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 === `${name}.exe` || base === 'flying-jib'
|
|
})
|
|
if (found) binary = found
|
|
}
|
|
|
|
if (fs.existsSync(binary)) {
|
|
try {
|
|
fs.chmodSync(binary, 0o755)
|
|
} catch {
|
|
/* win */
|
|
}
|
|
const flat = path.join(outDir, path.basename(binary))
|
|
if (path.resolve(binary) !== path.resolve(flat)) {
|
|
fs.copyFileSync(binary, flat)
|
|
binary = flat
|
|
}
|
|
console.log(`[bare-standalone] wrote ${binary}`)
|
|
} else {
|
|
console.warn(`[bare-standalone] WARN: binary not found under ${outDir}`)
|
|
try {
|
|
console.warn(
|
|
' contents:',
|
|
fs.readdirSync(outDir, { recursive: true }).slice(0, 40).join(', ')
|
|
)
|
|
} catch {
|
|
/* ignore */
|
|
}
|
|
}
|
|
|
|
fs.writeFileSync(
|
|
path.join(outDir, 'build-info.json'),
|
|
JSON.stringify(
|
|
{
|
|
host,
|
|
name,
|
|
version: pkg.version,
|
|
builtAt: new Date().toISOString(),
|
|
entry: 'bin.mjs',
|
|
bundleId: entry && entry.id,
|
|
minecraftDataPruned: !skipPrune
|
|
},
|
|
null,
|
|
2
|
|
) + '\n'
|
|
)
|
|
|
|
return outDir
|
|
}
|
|
|
|
async function main() {
|
|
const opts = parseArgs(process.argv.slice(2))
|
|
if (opts.help) {
|
|
console.log(`Usage: node scripts/bare-standalone.cjs [--host <host>|all] [--out dir]
|
|
Hosts: ${ALL_HOSTS.join(', ')}`)
|
|
process.exit(0)
|
|
}
|
|
|
|
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)
|
|
})
|