645 lines
21 KiB
JavaScript
645 lines
21 KiB
JavaScript
/**
|
|
* Electron Forge config for peardata-client.
|
|
*
|
|
* CI note: ignore list strips server/tooling deps.
|
|
* Only ship prebuilds for the package target platform/arch.
|
|
*
|
|
* QVAC: when `@qvac/sdk` is installed, `QvacForgePlugin` bundles the Bare worker,
|
|
* forces asar:false (required for .bare addons), and prunes foreign prebuilds.
|
|
* Set PEARDATA_SKIP_QVAC=1 to package a tools-only client without native QVAC.
|
|
*
|
|
* @see https://docs.qvac.tether.io/tutorials/electron
|
|
*/
|
|
'use strict'
|
|
|
|
const path = require('path')
|
|
const fs = require('fs')
|
|
|
|
const pkg = require('./package.json')
|
|
// Use lowercase package name for out/<name>-<host>/ so release staging is stable.
|
|
// productName (PearData) remains the display name in package.json / UI.
|
|
const appName = pkg.name || 'peardata'
|
|
|
|
/**
|
|
* Resolve packaging target from forge CLI args or env (set by make.cjs).
|
|
* @returns {{ platform: string, arch: string }}
|
|
*/
|
|
function resolvePackageTarget() {
|
|
const argv = process.argv
|
|
const flag = (name) => {
|
|
const i = argv.indexOf(name)
|
|
return i >= 0 && argv[i + 1] ? argv[i + 1] : null
|
|
}
|
|
const platform =
|
|
process.env.PEARDATA_PACKAGE_PLATFORM ||
|
|
flag('--platform') ||
|
|
process.platform
|
|
const arch =
|
|
process.env.PEARDATA_PACKAGE_ARCH || flag('--arch') || process.arch
|
|
return { platform, arch }
|
|
}
|
|
|
|
const packageTarget = resolvePackageTarget()
|
|
const packageHost = `${packageTarget.platform}-${packageTarget.arch}`
|
|
|
|
/** Hosts with published @qvac/llm-llamacpp Bare prebuilds (see scripts/hosts.cjs). */
|
|
const QVAC_NATIVE_HOSTS = new Set([
|
|
'linux-x64',
|
|
'linux-arm64',
|
|
'darwin-x64',
|
|
'darwin-arm64',
|
|
'win32-x64',
|
|
])
|
|
|
|
const skipQvacEnv =
|
|
process.env.PEARDATA_SKIP_QVAC === '1' || process.env.PEARDATA_SKIP_QVAC === 'true'
|
|
const skipQvacHost = !QVAC_NATIVE_HOSTS.has(packageHost)
|
|
const skipQvac = skipQvacEnv || skipQvacHost
|
|
|
|
if (skipQvacHost && !skipQvacEnv) {
|
|
console.warn(
|
|
`[forge] target ${packageHost} has no QVAC llm-llamacpp prebuild — packaging tools-only client`
|
|
)
|
|
}
|
|
|
|
/** @type {typeof import('@qvac/sdk/electron-forge')|null} */
|
|
let QvacForgePlugin = null
|
|
if (!skipQvac) {
|
|
try {
|
|
// Prefer THIS project's node_modules (never a parent hoist of an older SDK).
|
|
const forgePath = require.resolve('@qvac/sdk/electron-forge', {
|
|
paths: [__dirname],
|
|
})
|
|
QvacForgePlugin = require(forgePath)
|
|
} catch (err) {
|
|
console.warn(
|
|
'[forge] @qvac/sdk/electron-forge not available — packaging tools-only client.',
|
|
err?.message || err
|
|
)
|
|
}
|
|
}
|
|
const qvacEnabled = Boolean(QvacForgePlugin)
|
|
|
|
/** Path prefixes (packager paths start with /) to exclude from the app bundle */
|
|
const IGNORE_PREFIXES = [
|
|
'/.git',
|
|
'/.gitea',
|
|
'/.github',
|
|
'/out',
|
|
'/dist',
|
|
'/deploy',
|
|
'/test',
|
|
'/docs',
|
|
'/spec',
|
|
'/server',
|
|
'/bin',
|
|
'/scripts',
|
|
'/tools',
|
|
'/.cache',
|
|
'/build/stubs',
|
|
'/build/shims',
|
|
'/README.md',
|
|
'/LICENSE',
|
|
// Packaging / Bare server toolchain (not needed at Electron runtime)
|
|
'/node_modules/electron',
|
|
'/node_modules/electron-',
|
|
'/node_modules/@electron',
|
|
'/node_modules/@electron-forge',
|
|
'/node_modules/bare-build',
|
|
// bare-runtime is REQUIRED when QVAC is packaged (spawns Bare worker)
|
|
...(qvacEnabled ? [] : ['/node_modules/bare-runtime']),
|
|
'/node_modules/bare-sidecar',
|
|
// bare-link / bare-pack may be used by QVAC worker packaging — keep when QVAC on
|
|
...(qvacEnabled ? [] : ['/node_modules/bare-link', '/node_modules/bare-pack']),
|
|
'/node_modules/bare-lief',
|
|
'/node_modules/bare-apk',
|
|
'/node_modules/bare-app-image',
|
|
'/node_modules/bare-make',
|
|
'/node_modules/bare-dev',
|
|
'/node_modules/bare-bundle',
|
|
'/node_modules/bare-module-traverse',
|
|
'/node_modules/bare-sqlite',
|
|
'/node_modules/postject',
|
|
'/node_modules/@inquirer',
|
|
'/node_modules/terser',
|
|
'/node_modules/pear-runtime/',
|
|
'/node_modules/pear-electron',
|
|
// Heavy / unused tooling
|
|
'/node_modules/typescript',
|
|
'/node_modules/prettier',
|
|
'/node_modules/webpack',
|
|
'/node_modules/caniuse-lite',
|
|
'/node_modules/brittle',
|
|
'/node_modules/@types',
|
|
'/node_modules/esbuild',
|
|
'/node_modules/@esbuild',
|
|
'/node_modules/node-gyp',
|
|
'/node_modules/node-addon-api',
|
|
]
|
|
|
|
const IGNORE_REGEX = [
|
|
/^\/node_modules\/bare-build-/,
|
|
// bare-runtime-<host> handled specially below (QVAC needs target host binary)
|
|
/^\/node_modules\/bare-pack-/,
|
|
/^\/node_modules\/@esbuild\//,
|
|
/\.md$/i,
|
|
/\.map$/,
|
|
/\.d\.ts$/,
|
|
/^\/peardata-.*\.json$/,
|
|
/^\/\.env$/,
|
|
/^\/package-lock\.json$/,
|
|
/^\/node_modules\/[^/]+\/test\//,
|
|
/^\/node_modules\/[^/]+\/tests\//,
|
|
/^\/node_modules\/[^/]+\/docs\//,
|
|
/^\/node_modules\/[^/]+\/example\//,
|
|
/^\/node_modules\/[^/]+\/examples\//,
|
|
/^\/node_modules\/[^/]+\/\.github\//,
|
|
]
|
|
|
|
/**
|
|
* bare-runtime-darwin-arm64 etc. — keep only the package for the forge target host when QVAC is on.
|
|
* @param {string} file
|
|
*/
|
|
function isStrippedBareRuntimePlatform(file) {
|
|
if (!file.startsWith('/node_modules/bare-runtime-')) return false
|
|
// e.g. /node_modules/bare-runtime-darwin-arm64/...
|
|
const seg = file.split('/')[2] || ''
|
|
if (!seg.startsWith('bare-runtime-')) return false
|
|
if (!qvacEnabled) return true
|
|
const need = `bare-runtime-${packageHost}`
|
|
return seg !== need
|
|
}
|
|
|
|
function isForeignPrebuild(file) {
|
|
const marker = '/prebuilds/'
|
|
const idx = file.indexOf(marker)
|
|
if (idx === -1) return false
|
|
const host = file.slice(idx + marker.length).split('/')[0]
|
|
if (!host) return false
|
|
if (
|
|
host.startsWith('android') ||
|
|
host.startsWith('ios') ||
|
|
host.includes('simulator')
|
|
) {
|
|
return true
|
|
}
|
|
return host !== packageHost
|
|
}
|
|
|
|
function shouldIgnore(file) {
|
|
if (!file) return false
|
|
if (file === '/package.json') return false
|
|
if (file === '/electron/app.bundle.cjs') return false
|
|
if (file === '/qvac.config.json') return false
|
|
// Always keep QVAC worker entries + config for packaged SDK resolution
|
|
if (file === '/qvac' || file.startsWith('/qvac/')) return false
|
|
if (file === '/electron/app.bundle.cjs.map') {
|
|
return process.env.PEARDATA_KEEP_SOURCEMAP !== '1'
|
|
}
|
|
// Never strip @qvac/* when QVAC packaging is enabled (plugin also tree-shakes)
|
|
if (qvacEnabled && (file === '/node_modules/@qvac' || file.startsWith('/node_modules/@qvac/'))) {
|
|
// Still drop foreign prebuilds under @qvac
|
|
if (isForeignPrebuild(file)) return true
|
|
return false
|
|
}
|
|
// Keep bare-runtime + bare-runtime-<targetHost> for QVAC Bare worker spawn
|
|
if (qvacEnabled) {
|
|
if (file === '/node_modules/bare-runtime' || file.startsWith('/node_modules/bare-runtime/')) {
|
|
return false
|
|
}
|
|
const need = `/node_modules/bare-runtime-${packageHost}`
|
|
if (file === need || file.startsWith(need + '/')) return false
|
|
}
|
|
if (isStrippedBareRuntimePlatform(file)) return true
|
|
for (const p of IGNORE_PREFIXES) {
|
|
if (file === p || file.startsWith(p + '/') || file.startsWith(p)) return true
|
|
}
|
|
for (const re of IGNORE_REGEX) {
|
|
if (re.test(file)) return true
|
|
}
|
|
if (isForeignPrebuild(file)) return true
|
|
return false
|
|
}
|
|
|
|
const skipRebuild =
|
|
process.env.PEARDATA_FORCE_REBUILD !== '1' &&
|
|
process.env.PEARDATA_SKIP_REBUILD !== '0'
|
|
|
|
const electronZipDir = path.join(__dirname, '.cache', 'electron-zips')
|
|
const useElectronZipDir =
|
|
process.env.PEARDATA_USE_ELECTRON_ZIP_DIR !== '0' &&
|
|
fs.existsSync(electronZipDir)
|
|
|
|
/**
|
|
* Copy bare-runtime + bare-runtime-<host> (+ require-asset) into the staged app
|
|
* if packager omit them. Used when QVAC is enabled.
|
|
* @param {string} buildPath
|
|
* @param {string} host e.g. darwin-arm64
|
|
*/
|
|
function ensureBareRuntimeInBuild(buildPath, host) {
|
|
const nm = path.join(buildPath, 'node_modules')
|
|
const srcNm = path.join(__dirname, 'node_modules')
|
|
|
|
function copyPkg(name) {
|
|
const src = path.join(srcNm, name)
|
|
const dest = path.join(nm, name)
|
|
if (!fs.existsSync(src)) {
|
|
console.warn(`[forge] cannot copy ${name}: not in project node_modules`)
|
|
return false
|
|
}
|
|
if (fs.existsSync(dest)) {
|
|
// Ensure binary present even if dir exists empty-ish
|
|
return true
|
|
}
|
|
fs.mkdirSync(nm, { recursive: true })
|
|
fs.cpSync(src, dest, { recursive: true })
|
|
console.log(`[forge] copied ${name} into package node_modules`)
|
|
return true
|
|
}
|
|
|
|
copyPkg('bare-runtime')
|
|
copyPkg(`bare-runtime-${host}`)
|
|
// Platform package depends on require-asset (and its chain)
|
|
for (const dep of ['require-asset', 'bare-module-resolve', 'bare-semver']) {
|
|
if (fs.existsSync(path.join(srcNm, dep))) copyPkg(dep)
|
|
}
|
|
|
|
// Nest under bare-runtime/node_modules for require() from bare-runtime/index.js
|
|
const nestedParent = path.join(nm, 'bare-runtime', 'node_modules')
|
|
const nestedDest = path.join(nestedParent, `bare-runtime-${host}`)
|
|
const topDest = path.join(nm, `bare-runtime-${host}`)
|
|
if (fs.existsSync(topDest) && !fs.existsSync(nestedDest)) {
|
|
try {
|
|
fs.mkdirSync(nestedParent, { recursive: true })
|
|
fs.cpSync(topDest, nestedDest, { recursive: true })
|
|
console.log(`[forge] nested bare-runtime-${host} under bare-runtime/node_modules`)
|
|
} catch (err) {
|
|
console.warn(`[forge] nest bare-runtime-${host} failed:`, err?.message || err)
|
|
}
|
|
}
|
|
|
|
// Electron packager / copy can drop +x on the Bare binary
|
|
const binName = host.startsWith('win32') ? 'bare.exe' : 'bare'
|
|
for (const base of [topDest, nestedDest]) {
|
|
const bin = path.join(base, 'bin', binName)
|
|
if (fs.existsSync(bin)) {
|
|
try {
|
|
fs.chmodSync(bin, 0o755)
|
|
} catch {
|
|
// ignore
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function stripBuildPath(buildPath) {
|
|
const t0 = Date.now()
|
|
let removed = 0
|
|
|
|
function rm(rel) {
|
|
const p = path.join(buildPath, rel)
|
|
try {
|
|
if (fs.existsSync(p)) {
|
|
fs.rmSync(p, { recursive: true, force: true })
|
|
removed++
|
|
}
|
|
} catch {
|
|
// ignore
|
|
}
|
|
}
|
|
|
|
const junk = [
|
|
'node_modules/bare-sidecar',
|
|
'node_modules/electron',
|
|
'node_modules/@electron-forge',
|
|
'node_modules/bare-build',
|
|
// Keep bare-runtime when packaging QVAC (Bare worker spawn)
|
|
...(qvacEnabled ? [] : ['node_modules/bare-runtime']),
|
|
'node_modules/pear-runtime',
|
|
'node_modules/pear-electron',
|
|
'node_modules/esbuild',
|
|
'node_modules/bare-sqlite',
|
|
'node_modules/postject',
|
|
'node_modules/@inquirer',
|
|
'node_modules/terser',
|
|
'server',
|
|
'bin',
|
|
'scripts',
|
|
'spec',
|
|
'deploy',
|
|
'out',
|
|
'test',
|
|
'docs',
|
|
'tools',
|
|
'.cache',
|
|
'README.md',
|
|
'LICENSE',
|
|
]
|
|
for (const rel of junk) rm(rel)
|
|
|
|
const nm = path.join(buildPath, 'node_modules')
|
|
if (fs.existsSync(nm)) {
|
|
const stack = [nm]
|
|
const seen = new Set()
|
|
while (stack.length) {
|
|
const dir = stack.pop()
|
|
let real
|
|
try {
|
|
real = fs.realpathSync(dir)
|
|
} catch {
|
|
continue
|
|
}
|
|
if (seen.has(real)) continue
|
|
seen.add(real)
|
|
let entries
|
|
try {
|
|
entries = fs.readdirSync(dir, { withFileTypes: true })
|
|
} catch {
|
|
continue
|
|
}
|
|
for (const ent of entries) {
|
|
const full = path.join(dir, ent.name)
|
|
if (ent.isSymbolicLink()) continue
|
|
if (!ent.isDirectory()) continue
|
|
if (ent.name === 'prebuilds') {
|
|
let hosts
|
|
try {
|
|
hosts = fs.readdirSync(full)
|
|
} catch {
|
|
continue
|
|
}
|
|
for (const host of hosts) {
|
|
if (host === packageHost) continue
|
|
try {
|
|
fs.rmSync(path.join(full, host), { recursive: true, force: true })
|
|
removed++
|
|
} catch {
|
|
// ignore
|
|
}
|
|
}
|
|
continue
|
|
}
|
|
if (ent.name === '.bin') continue
|
|
stack.push(full)
|
|
}
|
|
}
|
|
}
|
|
|
|
if (process.env.PEARDATA_KEEP_SOURCEMAP !== '1') {
|
|
rm('electron/app.bundle.cjs.map')
|
|
}
|
|
|
|
console.log(
|
|
`[forge] packageAfterCopy target=${packageHost} stripped=${removed} in ${Date.now() - t0}ms`
|
|
)
|
|
}
|
|
|
|
module.exports = {
|
|
packagerConfig: {
|
|
name: appName,
|
|
executableName: 'peardata-client',
|
|
appBundleId: 'com.peardata.app',
|
|
icon: fs.existsSync(path.join(__dirname, 'build', 'icon.png'))
|
|
? path.join(__dirname, 'build', 'icon')
|
|
: undefined,
|
|
// QvacForgePlugin forces asar:false when QVAC is enabled (Bare cannot load from asar).
|
|
// Without QVAC, keep asar + native unpack for smaller zips.
|
|
asar: qvacEnabled
|
|
? false
|
|
: {
|
|
unpack: '**/*.{node,bare,dll,dylib,so}',
|
|
},
|
|
ignore: shouldIgnore,
|
|
derefSymlinks: false,
|
|
prune: false,
|
|
...(useElectronZipDir ? { electronZipDir } : {}),
|
|
quiet: process.env.CI ? false : true,
|
|
osxSign: false,
|
|
},
|
|
|
|
rebuildConfig: skipRebuild
|
|
? { onlyModules: [], force: false }
|
|
: {
|
|
force: false,
|
|
onlyModules: [
|
|
'udx-native',
|
|
'sodium-native',
|
|
'rocksdb-native',
|
|
'fs-native-extensions',
|
|
'quickbit-native',
|
|
'simdle-native',
|
|
'bare-fs',
|
|
'bare-os',
|
|
'bare-crypto',
|
|
],
|
|
},
|
|
|
|
makers: [
|
|
{
|
|
name: '@electron-forge/maker-zip',
|
|
platforms: ['darwin', 'linux', 'win32'],
|
|
},
|
|
],
|
|
|
|
plugins: qvacEnabled
|
|
? [
|
|
new QvacForgePlugin({
|
|
projectDir: __dirname,
|
|
configPath: path.join(__dirname, 'qvac.config.json'),
|
|
// Host comes from electron-forge --platform/--arch (make.cjs sets these)
|
|
logLevel: process.env.CI ? 'info' : 'info',
|
|
}),
|
|
]
|
|
: [],
|
|
|
|
hooks: {
|
|
prePackage: async () => {
|
|
console.log(`[forge] packaging target host: ${packageHost}`)
|
|
console.log(
|
|
`[forge] QVAC: ${qvacEnabled ? 'enabled (@qvac/sdk/electron-forge)' : 'skipped (tools-only client)'}`
|
|
)
|
|
console.log(
|
|
`[forge] electronZipDir: ${useElectronZipDir ? electronZipDir : '(none — packager will download)'}`
|
|
)
|
|
console.log(
|
|
`[forge] rebuild: ${skipRebuild ? 'skip (onlyModules:[])' : 'enabled'}`
|
|
)
|
|
|
|
// QVAC Bare worker needs bare-runtime + platform binary in the app package.
|
|
// On Linux CI, optional deps skip darwin/win32 — force-install the target host.
|
|
if (qvacEnabled) {
|
|
try {
|
|
require('child_process').execFileSync(
|
|
process.execPath,
|
|
[
|
|
path.join(__dirname, 'scripts', 'ensure-qvac-bare-runtimes.cjs'),
|
|
'--hosts',
|
|
packageHost,
|
|
],
|
|
{ stdio: 'inherit', cwd: __dirname }
|
|
)
|
|
} catch (err) {
|
|
throw new Error(
|
|
`[forge] failed to ensure bare-runtime-${packageHost} for QVAC: ${err?.message || err}`
|
|
)
|
|
}
|
|
// bundleSdk (QvacForgePlugin) may regenerate absolute file:// imports —
|
|
// always re-write a portable entry before packaging copies files.
|
|
try {
|
|
require('./scripts/rewrite-qvac-worker-entry.cjs').ensurePortableWorkerEntry({
|
|
root: __dirname,
|
|
forcePortable: true,
|
|
})
|
|
} catch (err) {
|
|
console.warn('[forge] prePackage portable worker rewrite:', err?.message || err)
|
|
}
|
|
}
|
|
|
|
if (process.env.PEARDATA_SKIP_PREPACKAGE_BUNDLE === '1') {
|
|
const bundle = path.join(__dirname, 'electron', 'app.bundle.cjs')
|
|
if (fs.existsSync(bundle)) {
|
|
console.log('[forge] prePackage: skip GUI bundle (already built)')
|
|
return
|
|
}
|
|
}
|
|
require('child_process').execFileSync(
|
|
process.execPath,
|
|
[path.join(__dirname, 'scripts', 'build-client-bundle.cjs')],
|
|
{ stdio: 'inherit', cwd: __dirname }
|
|
)
|
|
},
|
|
preMake: async () => {
|
|
fs.rmSync(path.join(__dirname, 'out', 'make'), { recursive: true, force: true })
|
|
},
|
|
packageAfterCopy: async (_forgeConfig, buildPath, _electronVersion, platform, arch) => {
|
|
// Prefer packager's real platform/arch (authoritative for this package).
|
|
const host =
|
|
platform && arch ? `${platform}-${arch}` : packageHost
|
|
|
|
const pkgPath = path.join(buildPath, 'package.json')
|
|
const appPkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'))
|
|
appPkg.main = 'electron/main.cjs'
|
|
delete appPkg.devDependencies
|
|
delete appPkg.scripts
|
|
if (appPkg.dependencies) {
|
|
const drop = ['bare-build', 'bare-sqlite', 'pear-electron']
|
|
// bare-runtime stays when QVAC ships (worker spawn)
|
|
if (!qvacEnabled) drop.push('bare-runtime')
|
|
for (const name of drop) {
|
|
delete appPkg.dependencies[name]
|
|
}
|
|
}
|
|
fs.writeFileSync(pkgPath, JSON.stringify(appPkg, null, 2) + '\n')
|
|
|
|
// Ensure worker entry exists for SDK resolvePackagedWorkerPath()
|
|
const workerCandidates = [
|
|
path.join(buildPath, 'qvac', 'worker.entry.mjs'),
|
|
path.join(buildPath, 'qvac', 'worker.bundle.js'),
|
|
]
|
|
const hasWorker = workerCandidates.some((p) => fs.existsSync(p))
|
|
if (qvacEnabled && !hasWorker) {
|
|
console.warn(
|
|
'[forge] warning: no qvac worker entry in package — full LLM may fail; tools-only still works'
|
|
)
|
|
}
|
|
|
|
// Portable worker.entry.mjs: strip absolute file:///build-machine paths
|
|
// (bundleSdk emits those; Bare then dies → RPC init timeout 30s)
|
|
if (qvacEnabled) {
|
|
try {
|
|
const { ensurePortableWorkerEntry } = require('./scripts/rewrite-qvac-worker-entry.cjs')
|
|
const wr = ensurePortableWorkerEntry({
|
|
root: buildPath,
|
|
forcePortable: true,
|
|
})
|
|
console.log(
|
|
`[forge] portable QVAC worker entry: ${wr.reason || 'ok'} → ${wr.path}`
|
|
)
|
|
} catch (err) {
|
|
console.warn('[forge] rewrite-qvac-worker-entry failed:', err?.message || err)
|
|
}
|
|
}
|
|
|
|
// --- bare-runtime platform binary (QVAC issue #1492) -------------------
|
|
// If the platform package was missed by ignore/prune, copy it from the
|
|
// project tree. Fail hard when QVAC is enabled so CI never ships a
|
|
// "tools-only with cryptic bare-runtime error" client.
|
|
if (qvacEnabled) {
|
|
ensureBareRuntimeInBuild(buildPath, host)
|
|
}
|
|
|
|
stripBuildPath(buildPath)
|
|
|
|
// Re-check after strip (strip must not remove target host runtime)
|
|
if (qvacEnabled) {
|
|
const binName = host.startsWith('win32') ? 'bare.exe' : 'bare'
|
|
const bareBin = path.join(
|
|
buildPath,
|
|
'node_modules',
|
|
`bare-runtime-${host}`,
|
|
'bin',
|
|
binName
|
|
)
|
|
const bareMeta = path.join(buildPath, 'node_modules', 'bare-runtime', 'package.json')
|
|
if (!fs.existsSync(bareMeta) || !fs.existsSync(bareBin)) {
|
|
throw new Error(
|
|
`[forge] QVAC package missing bare-runtime for ${host}.\n` +
|
|
` bare-runtime: ${fs.existsSync(bareMeta) ? 'ok' : 'MISSING'}\n` +
|
|
` ${bareBin}: ${fs.existsSync(bareBin) ? 'ok' : 'MISSING'}\n` +
|
|
` Run: node scripts/ensure-qvac-bare-runtimes.cjs --hosts ${host}\n` +
|
|
` See https://github.com/tetherto/qvac/issues/1492`
|
|
)
|
|
}
|
|
console.log(`[forge] verified bare-runtime-${host} binary in package`)
|
|
}
|
|
|
|
// Fail if worker still has build-machine absolute imports
|
|
if (qvacEnabled) {
|
|
const entry = path.join(buildPath, 'qvac', 'worker.entry.mjs')
|
|
if (fs.existsSync(entry)) {
|
|
const body = fs.readFileSync(entry, 'utf8')
|
|
if (/file:\/\/\/Users\/|file:\/\/\/home\/|file:\/\/\/[A-Za-z]:\//.test(body)) {
|
|
throw new Error(
|
|
`[forge] qvac/worker.entry.mjs still has absolute file:// imports — ` +
|
|
`Bare worker will time out on other machines. Run: ` +
|
|
`node scripts/rewrite-qvac-worker-entry.cjs --force-portable`
|
|
)
|
|
}
|
|
console.log('[forge] verified portable qvac/worker.entry.mjs (no absolute file://)')
|
|
} else {
|
|
throw new Error(
|
|
`[forge] missing qvac/worker.entry.mjs in package — Bare RPC cannot start`
|
|
)
|
|
}
|
|
// Ensure @qvac/sdk paths the worker imports exist
|
|
const needFiles = [
|
|
'node_modules/@qvac/sdk/dist/server/worker-core.js',
|
|
'node_modules/@qvac/sdk/dist/server/plugins/index.js',
|
|
'node_modules/@qvac/sdk/dist/logging/index.js',
|
|
'node_modules/@qvac/sdk/dist/server/bare/plugins/llamacpp-completion/plugin.js',
|
|
]
|
|
for (const rel of needFiles) {
|
|
if (!fs.existsSync(path.join(buildPath, rel))) {
|
|
throw new Error(
|
|
`[forge] QVAC package missing ${rel} (required by portable worker entry)`
|
|
)
|
|
}
|
|
}
|
|
}
|
|
},
|
|
postPackage: async (_forgeConfig, options) => {
|
|
const platform = options.platform || process.platform
|
|
if (platform !== 'darwin') return
|
|
const { signApp, findApps } = require('./scripts/sign-macos-app.cjs')
|
|
const paths = options.outputPaths || []
|
|
for (const outPath of paths) {
|
|
const apps = findApps(outPath)
|
|
for (const app of apps) {
|
|
console.log('[forge] postPackage codesign', app)
|
|
await signApp(app)
|
|
}
|
|
}
|
|
},
|
|
},
|
|
}
|