Updates
CI / test (push) Successful in 2m37s
Release rolling / release (push) Successful in 11m29s

This commit is contained in:
Raven Scott
2026-07-30 15:45:33 -04:00
parent 4b5b78892f
commit 726dbdfd56
10 changed files with 523 additions and 28 deletions
+7 -1
View File
@@ -95,6 +95,11 @@ jobs:
echo "==> esbuild binary" echo "==> esbuild binary"
node node_modules/esbuild/install.js node node_modules/esbuild/install.js
node -e "console.log('node', process.version); console.log('esbuild', require('esbuild').version)" node -e "console.log('node', process.version); console.log('esbuild', require('esbuild').version)"
# QVAC: npm ci skips os/cpu-mismatched optional bare-runtime-* packages.
# Force-install every client host binary so Linux CI can package darwin/win32.
# https://github.com/tetherto/qvac/issues/1492
echo "==> ensure QVAC bare-runtime platform packages"
node scripts/ensure-qvac-bare-runtimes.cjs --all
- name: Unit tests - name: Unit tests
timeout-minutes: 10 timeout-minutes: 10
@@ -141,7 +146,8 @@ jobs:
ELECTRON_CACHE: ${{ github.workspace }}/.cache/electron ELECTRON_CACHE: ${{ github.workspace }}/.cache/electron
electron_config_cache: ${{ github.workspace }}/.cache/electron electron_config_cache: ${{ github.workspace }}/.cache/electron
PEARDATA_ELECTRON_DOWNLOAD_TIMEOUT_MS: '180000' PEARDATA_ELECTRON_DOWNLOAD_TIMEOUT_MS: '180000'
PEARDATA_CLIENT_TIMEOUT_MS: '480000' # QVAC bundleSdk + forge package is heavy; 8m was tight for multi-host matrix
PEARDATA_CLIENT_TIMEOUT_MS: '1200000'
ELECTRON_GET_USE_PROXY: '0' ELECTRON_GET_USE_PROXY: '0'
CI: 'true' CI: 'true'
run: | run: |
+3 -1
View File
@@ -58,6 +58,8 @@ jobs:
git config --global url."https://github.com/".insteadOf "[email protected]:" git config --global url."https://github.com/".insteadOf "[email protected]:"
npm ci --ignore-scripts --no-audit --no-fund npm ci --ignore-scripts --no-audit --no-fund
node node_modules/esbuild/install.js node node_modules/esbuild/install.js
# Cross-host bare-runtime binaries for QVAC Electron clients (issue #1492)
node scripts/ensure-qvac-bare-runtimes.cjs --all
- name: Unit tests - name: Unit tests
env: env:
@@ -87,7 +89,7 @@ jobs:
PEARDATA_SKIP_REBUILD: '1' PEARDATA_SKIP_REBUILD: '1'
ELECTRON_CACHE: ${{ github.workspace }}/.cache/electron ELECTRON_CACHE: ${{ github.workspace }}/.cache/electron
electron_config_cache: ${{ github.workspace }}/.cache/electron electron_config_cache: ${{ github.workspace }}/.cache/electron
PEARDATA_CLIENT_TIMEOUT_MS: '480000' PEARDATA_CLIENT_TIMEOUT_MS: '1200000'
CI: 'true' CI: 'true'
run: | run: |
set -euo pipefail set -euo pipefail
+30
View File
@@ -68,10 +68,39 @@ DRY_RUN=1 bash scripts/gitea-rolling-release.sh
| Forge plugin | `@qvac/sdk/electron-forge` + `@electron-forge/plugin-base` | | Forge plugin | `@qvac/sdk/electron-forge` + `@electron-forge/plugin-base` |
| Config | `qvac.config.json` — LLM plugin only | | Config | `qvac.config.json` — LLM plugin only |
| Asar | Forced **off** when QVAC packaging is enabled (Bare needs real paths) | | Asar | Forced **off** when QVAC packaging is enabled (Bare needs real paths) |
| Bare runtime | `bare-runtime` + `bare-runtime-<host>` must be **inside** the app `node_modules` |
| Skip | `PEARDATA_SKIP_QVAC=1` → tools-only client | | Skip | `PEARDATA_SKIP_QVAC=1` → tools-only client |
| Auto tools-only | `win32-arm64` (no `@qvac/llm-llamacpp` prebuild) — still ships client | | Auto tools-only | `win32-arm64` (no `@qvac/llm-llamacpp` prebuild) — still ships client |
| Docs | [QVAC.md](./QVAC.md) · [Electron tutorial](https://docs.qvac.tether.io/tutorials/electron) | | Docs | [QVAC.md](./QVAC.md) · [Electron tutorial](https://docs.qvac.tether.io/tutorials/electron) |
### Cross-host Bare runtime (issue #1492)
CI runs on **Linux** but packages **darwin/win32** clients. `npm ci` skips optional
`bare-runtime-darwin-*` / `bare-runtime-win32-*` packages (os/cpu filters). Without them
the packaged app falls back to:
```text
Ready (tools-only: Could not load the Bare runtime binary for darwin-arm64 …
```
Release workflows and `make.cjs` install platform binaries via
`scripts/ensure-qvac-bare-runtimes.cjs`, which **`npm pack` + extracts** each
`bare-runtime-<host>` into `node_modules` (plain `npm install --force` is not
enough — npm's os/cpu filters drop foreign packages and can prune siblings).
```bash
# after npm ci on the Linux runner
node scripts/ensure-qvac-bare-runtimes.cjs --all
# or only hosts you will package
node scripts/ensure-qvac-bare-runtimes.cjs --hosts darwin-arm64,darwin-x64,linux-x64,win32-x64
```
`forge.config.cjs` also:
1. Runs the ensure script in `prePackage` for the target host
2. Copies `bare-runtime` + `bare-runtime-<host>` into the app if packager omitted them
3. **Fails the package** if the Bare binary is still missing when QVAC is enabled
Client package default timeout is **20 minutes** when QVAC is enabled (`PEARDATA_CLIENT_TIMEOUT_MS`). Client package default timeout is **20 minutes** when QVAC is enabled (`PEARDATA_CLIENT_TIMEOUT_MS`).
## Key scripts ## Key scripts
@@ -84,6 +113,7 @@ Client package default timeout is **20 minutes** when QVAC is enabled (`PEARDATA
| `scripts/bare-standalone.cjs` | Pack Bare `peardata-server` | | `scripts/bare-standalone.cjs` | Pack Bare `peardata-server` |
| `scripts/build-client-bundle.cjs` | esbuild GUI → `electron/app.bundle.cjs` | | `scripts/build-client-bundle.cjs` | esbuild GUI → `electron/app.bundle.cjs` |
| `scripts/build-qvac-worker.cjs` | Local `bundleSdk` for QVAC Bare worker | | `scripts/build-qvac-worker.cjs` | Local `bundleSdk` for QVAC Bare worker |
| `scripts/ensure-qvac-bare-runtimes.cjs` | Force-install `bare-runtime-<host>` for cross-host QVAC clients |
| `scripts/predownload-electron.cjs` | Pre-fetch Electron zips for CI | | `scripts/predownload-electron.cjs` | Pre-fetch Electron zips for CI |
| `scripts/sign-macos-app.cjs` | Darwin client codesign | | `scripts/sign-macos-app.cjs` | Darwin client codesign |
| `scripts/gitea-rolling-release.sh` | Build + stage + Gitea upload | | `scripts/gitea-rolling-release.sh` | Build + stage + Gitea upload |
+10 -2
View File
@@ -78,17 +78,25 @@ Produces a **tools-only** client (no LLM natives). QVAC tab still works via RPC
QVAC spawns a **Bare** worker via `bare-runtime` + a platform package such as `bare-runtime-darwin-arm64`. QVAC spawns a **Bare** worker via `bare-runtime` + a platform package such as `bare-runtime-darwin-arm64`.
These ship as **production dependencies** and are **not** stripped by forge for the package target host. If you see: These are listed under **optionalDependencies** (os/cpu-filtered). On a Linux CI runner
`npm ci` will **not** install the darwin/win32 packages, so packaging must force-install them
first (see `scripts/ensure-qvac-bare-runtimes.cjs` and [CI.md](./CI.md)).
Forge keeps `bare-runtime` + `bare-runtime-<targetHost>` in the app and **fails the build**
if the binary is missing when QVAC packaging is enabled.
If you still see:
```text ```text
Could not load the Bare runtime binary for darwin-arm64 Could not load the Bare runtime binary for darwin-arm64
… bare-runtime-darwin-arm64 … missing … bare-runtime-darwin-arm64 … missing
``` ```
reinstall deps and rebuild the client: on a local or CI client:
```bash ```bash
npm ci npm ci
npm run ensure:qvac-bare-runtimes # force-install all QVAC host binaries
npm run make:client:darwin-arm64 npm run make:client:darwin-arm64
``` ```
+109 -22
View File
@@ -230,6 +230,68 @@ const useElectronZipDir =
process.env.PEARDATA_USE_ELECTRON_ZIP_DIR !== '0' && process.env.PEARDATA_USE_ELECTRON_ZIP_DIR !== '0' &&
fs.existsSync(electronZipDir) 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) { function stripBuildPath(buildPath) {
const t0 = Date.now() const t0 = Date.now()
let removed = 0 let removed = 0
@@ -403,33 +465,23 @@ module.exports = {
`[forge] rebuild: ${skipRebuild ? 'skip (onlyModules:[])' : 'enabled'}` `[forge] rebuild: ${skipRebuild ? 'skip (onlyModules:[])' : 'enabled'}`
) )
// QVAC Bare worker needs bare-runtime + platform binary in the app package // 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) { if (qvacEnabled) {
const platPkg = `bare-runtime-${packageHost}` try {
const platDir = path.join(__dirname, 'node_modules', platPkg)
if (!fs.existsSync(platDir)) {
let ver = '1.30.3'
try {
ver = require('bare-runtime/package.json').version
} catch {
// default
}
console.log(`[forge] installing ${platPkg}@${ver} for QVAC worker…`)
// --force: allow cross-host installs (linux CI packaging darwin/win32)
require('child_process').execFileSync( require('child_process').execFileSync(
process.platform === 'win32' ? 'npm.cmd' : 'npm', process.execPath,
[ [
'install', path.join(__dirname, 'scripts', 'ensure-qvac-bare-runtimes.cjs'),
`${platPkg}@${ver}`, '--hosts',
'--no-save', packageHost,
'--no-audit',
'--no-fund',
'--force',
], ],
{ stdio: 'inherit', cwd: __dirname } { stdio: 'inherit', cwd: __dirname }
) )
} else { } catch (err) {
console.log(`[forge] QVAC bare platform runtime present: ${platPkg}`) throw new Error(
`[forge] failed to ensure bare-runtime-${packageHost} for QVAC: ${err?.message || err}`
)
} }
} }
@@ -449,7 +501,11 @@ module.exports = {
preMake: async () => { preMake: async () => {
fs.rmSync(path.join(__dirname, 'out', 'make'), { recursive: true, force: true }) fs.rmSync(path.join(__dirname, 'out', 'make'), { recursive: true, force: true })
}, },
packageAfterCopy: async (_forgeConfig, buildPath) => { 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 pkgPath = path.join(buildPath, 'package.json')
const appPkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8')) const appPkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'))
appPkg.main = 'electron/main.cjs' appPkg.main = 'electron/main.cjs'
@@ -477,7 +533,38 @@ module.exports = {
) )
} }
// --- 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) 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`)
}
}, },
postPackage: async (_forgeConfig, options) => { postPackage: async (_forgeConfig, options) => {
const platform = options.platform || process.platform const platform = options.platform || process.platform
+1
View File
@@ -55,6 +55,7 @@
"release:notes": "node -e \"console.log('See docs/RELEASE.md')\"", "release:notes": "node -e \"console.log('See docs/RELEASE.md')\"",
"build:client-bundle": "node scripts/build-client-bundle.cjs", "build:client-bundle": "node scripts/build-client-bundle.cjs",
"build:qvac-worker": "node scripts/build-qvac-worker.cjs", "build:qvac-worker": "node scripts/build-qvac-worker.cjs",
"ensure:qvac-bare-runtimes": "node scripts/ensure-qvac-bare-runtimes.cjs --all",
"start:client": "npm run build:client-bundle && electron electron/main.cjs", "start:client": "npm run build:client-bundle && electron electron/main.cjs",
"package:client": "electron-forge package", "package:client": "electron-forge package",
"make": "node scripts/make.cjs both", "make": "node scripts/make.cjs both",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"version": 1, "version": 1,
"bundleId": "03a99e64d3131c8a1eb6aedca6edac581d98e849be23db165c1d0c33f24d06d8", "bundleId": "21c59a9e2c57a1e5e2d06d5b19ec0c7727dbffaf779dba102ff91aaa3207e3f7",
"addons": [ "addons": [
"@qvac/llm-llamacpp", "@qvac/llm-llamacpp",
"bare-buffer", "bare-buffer",
File diff suppressed because one or more lines are too long
+342
View File
@@ -0,0 +1,342 @@
#!/usr/bin/env node
/**
* Force-install bare-runtime platform binaries needed for QVAC Electron clients.
*
* Why this exists
* ---------------
* `bare-runtime` resolves the worker binary via:
* require(`bare-runtime-${platform}-${arch}`)
* Those packages are **optionalDependencies** with `os`/`cpu` filters, so
* `npm ci` on a Linux CI runner never installs darwin/win32 platform packages.
* Cross-compiling clients then packages an app that fails at runtime with:
* Could not load the Bare runtime binary for darwin-arm64
* … bare-runtime-darwin-arm64 … missing
* (https://github.com/tetherto/qvac/issues/1492)
*
* Usage
* -----
* node scripts/ensure-qvac-bare-runtimes.cjs
* node scripts/ensure-qvac-bare-runtimes.cjs --hosts darwin-arm64,linux-x64
* node scripts/ensure-qvac-bare-runtimes.cjs --all
*
* Env:
* PEARDATA_CLIENT_HOSTS comma list (default: all QVAC-native hosts)
* PEARDATA_SKIP_QVAC=1 no-op exit 0
*/
'use strict'
const fs = require('fs')
const path = require('path')
const { execFileSync } = require('child_process')
const { QVAC_NATIVE_HOSTS, parseHostList, ALL_64 } = require('./hosts.cjs')
const root = path.resolve(__dirname, '..')
function readBareRuntimeVersion() {
try {
// exports map uses "./package" not "./package.json"
const pkgPath = require.resolve('bare-runtime/package', { paths: [root] })
return require(pkgPath).version
} catch {
try {
return require(path.join(root, 'node_modules', 'bare-runtime', 'package.json'))
.version
} catch {
const rootPkg = require(path.join(root, 'package.json'))
const v =
rootPkg.dependencies?.['bare-runtime'] ||
rootPkg.optionalDependencies?.['bare-runtime'] ||
'1.30.3'
return String(v).replace(/^[\^~]/, '')
}
}
}
/**
* @param {string} host e.g. darwin-arm64
* @returns {string} path to bare binary inside the platform package
*/
function bareBinaryPath(host) {
const [platform] = host.split('-')
const bin = platform === 'win32' ? 'bare.exe' : 'bare'
return path.join(root, 'node_modules', `bare-runtime-${host}`, 'bin', bin)
}
/**
* @param {string} host
* @returns {boolean}
*/
function hasPlatformPackage(host) {
const pkgJson = path.join(
root,
'node_modules',
`bare-runtime-${host}`,
'package.json'
)
if (!fs.existsSync(pkgJson)) return false
return fs.existsSync(bareBinaryPath(host))
}
/**
* Install one platform package without npm's os/cpu prune of siblings.
*
* Why not `npm install --force --os=… --cpu=…`?
* Each install with a foreign --os/--cpu **removes** other bare-runtime-*
* packages that don't match that os/cpu. Cross-compiling the full client
* matrix needs linux + darwin + win32 packages side-by-side on the runner.
*
* Approach: `npm pack` the tarball → extract into node_modules/<name>.
* Also ensure `require-asset` (dependency of platform packages) is present.
*
* @param {string} host e.g. darwin-arm64
* @param {string} version
*/
function installPlatformPackage(host, version) {
const name = `bare-runtime-${host}`
const spec = `${name}@${version}`
const dest = path.join(root, 'node_modules', name)
const tmp = fs.mkdtempSync(path.join(require('os').tmpdir(), 'peardata-bare-'))
const npm = process.platform === 'win32' ? 'npm.cmd' : 'npm'
console.log(`[ensure-bare-runtime] packing ${spec}`)
try {
const out = execFileSync(npm, ['pack', spec, '--pack-destination', tmp], {
cwd: root,
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'pipe'],
env: { ...process.env, npm_config_fund: 'false', npm_config_audit: 'false' },
})
const tgzName = String(out)
.trim()
.split(/\r?\n/)
.filter(Boolean)
.pop()
if (!tgzName) throw new Error(`npm pack produced no tarball for ${spec}`)
const tgz = path.join(tmp, tgzName)
if (!fs.existsSync(tgz)) throw new Error(`missing tarball ${tgz}`)
// Extract package/* → node_modules/<name>/
if (fs.existsSync(dest)) {
fs.rmSync(dest, { recursive: true, force: true })
}
fs.mkdirSync(dest, { recursive: true })
// npm pack layout: package/bin/bare, package/package.json, …
execFileSync('tar', ['-xzf', tgz, '-C', dest, '--strip-components=1'], {
stdio: 'inherit',
})
// Ensure the Bare binary is executable (tar + some FS syncs drop +x)
chmodBareBinary(dest, host)
console.log(`[ensure-bare-runtime] extracted ${name} → node_modules/${name}`)
} finally {
try {
fs.rmSync(tmp, { recursive: true, force: true })
} catch {
// ignore
}
}
// Platform packages depend on require-asset (and its small dep chain).
ensureRequireAsset()
}
/**
* @param {string} pkgDir node_modules/bare-runtime-<host>
* @param {string} host
*/
function chmodBareBinary(pkgDir, host) {
const binName = host.startsWith('win32') ? 'bare.exe' : 'bare'
const bin = path.join(pkgDir, 'bin', binName)
if (!fs.existsSync(bin)) return
try {
fs.chmodSync(bin, 0o755)
} catch (err) {
console.warn(`[ensure-bare-runtime] chmod ${bin}:`, err?.message || err)
}
}
/**
* Ensure require-asset is available for platform package index.js
* (they do: require.asset = require('require-asset')).
*/
function ensureRequireAsset() {
const dest = path.join(root, 'node_modules', 'require-asset')
if (fs.existsSync(path.join(dest, 'package.json'))) return
const npm = process.platform === 'win32' ? 'npm.cmd' : 'npm'
console.log('[ensure-bare-runtime] installing require-asset (platform package dep)…')
execFileSync(
npm,
['install', 'require-asset@^1.0.2', '--no-save', '--no-audit', '--no-fund'],
{ cwd: root, stdio: 'inherit' }
)
}
/**
* Also mirror into bare-runtime/node_modules so nested require() always finds it
* (matches how bare-runtime optionalDeps would look when installed on-target).
* @param {string} host
*/
function linkIntoBareRuntime(host) {
const name = `bare-runtime-${host}`
const src = path.join(root, 'node_modules', name)
const bareRt = path.join(root, 'node_modules', 'bare-runtime')
if (!fs.existsSync(src) || !fs.existsSync(bareRt)) return
const destParent = path.join(bareRt, 'node_modules')
const dest = path.join(destParent, name)
try {
fs.mkdirSync(destParent, { recursive: true })
if (fs.existsSync(dest)) {
const st = fs.lstatSync(dest)
if (st.isSymbolicLink() || st.isDirectory()) {
// Already present — leave alone if realpath matches
try {
if (fs.realpathSync(dest) === fs.realpathSync(src)) return
} catch {
// replace
}
fs.rmSync(dest, { recursive: true, force: true })
}
}
// Prefer junction/symlink on same volume; fall back to copy
try {
fs.symlinkSync(src, dest, process.platform === 'win32' ? 'junction' : 'dir')
console.log(`[ensure-bare-runtime] linked ${name} → bare-runtime/node_modules/`)
} catch {
fs.cpSync(src, dest, { recursive: true })
console.log(`[ensure-bare-runtime] copied ${name} → bare-runtime/node_modules/`)
}
} catch (err) {
console.warn(
`[ensure-bare-runtime] warn: could not nest ${name}:`,
err?.message || err
)
}
}
/**
* @param {string[]} hosts
*/
function ensure(hosts) {
if (
process.env.PEARDATA_SKIP_QVAC === '1' ||
process.env.PEARDATA_SKIP_QVAC === 'true'
) {
console.log('[ensure-bare-runtime] skipped (PEARDATA_SKIP_QVAC=1)')
return { skipped: true, hosts: [] }
}
const version = readBareRuntimeVersion()
console.log(
`[ensure-bare-runtime] bare-runtime@${version}; hosts=${hosts.join(',') || '(none)'}`
)
// Parent package must exist
const bareRoot = path.join(root, 'node_modules', 'bare-runtime')
if (!fs.existsSync(bareRoot)) {
console.log(`[ensure-bare-runtime] installing bare-runtime@${version}`)
const npm = process.platform === 'win32' ? 'npm.cmd' : 'npm'
execFileSync(
npm,
['install', `bare-runtime@${version}`, '--no-save', '--no-audit', '--no-fund', '--force'],
{ cwd: root, stdio: 'inherit' }
)
}
/** @type {string[]} */
const installed = []
/** @type {string[]} */
const missing = []
for (const host of hosts) {
if (!/^[a-z0-9]+-[a-z0-9]+$/i.test(host)) {
console.warn(`[ensure-bare-runtime] skip invalid host: ${host}`)
continue
}
// win32-arm64 has no bare-runtime package published? check optional list — it is published
if (hasPlatformPackage(host)) {
// Re-apply +x (packages copied from CI artifacts / packagers may lose it)
chmodBareBinary(path.join(root, 'node_modules', `bare-runtime-${host}`), host)
console.log(`[ensure-bare-runtime] ok (present): bare-runtime-${host}`)
linkIntoBareRuntime(host)
installed.push(host)
continue
}
try {
installPlatformPackage(host, version)
} catch (err) {
console.error(
`[ensure-bare-runtime] FAILED to install bare-runtime-${host}:`,
err?.message || err
)
missing.push(host)
continue
}
if (!hasPlatformPackage(host)) {
console.error(
`[ensure-bare-runtime] install reported ok but binary missing: ${bareBinaryPath(host)}`
)
missing.push(host)
continue
}
linkIntoBareRuntime(host)
installed.push(host)
}
if (missing.length) {
const msg =
`[ensure-bare-runtime] missing platform packages after install: ${missing.join(', ')}\n` +
`QVAC clients for those hosts will fail with BareRuntimeBinaryNotFoundError.\n` +
`See https://github.com/tetherto/qvac/issues/1492`
console.error(msg)
process.exitCode = 1
return { ok: false, installed, missing }
}
console.log(
`[ensure-bare-runtime] ready: ${installed.map((h) => `bare-runtime-${h}`).join(', ')}`
)
return { ok: true, installed, missing: [] }
}
function parseArgs(argv) {
/** @type {string[]|null} */
let hosts = null
let all = false
for (let i = 0; i < argv.length; i++) {
const a = argv[i]
if (a === '--all') {
all = true
} else if (a === '--hosts' && argv[i + 1]) {
hosts = parseHostList(argv[++i], [])
} else if (a.startsWith('--hosts=')) {
hosts = parseHostList(a.slice('--hosts='.length), [])
} else if (a === '--help' || a === '-h') {
console.log(`Usage: node scripts/ensure-qvac-bare-runtimes.cjs [--all | --hosts a,b]`)
process.exit(0)
}
}
if (all) {
// All client hosts that can ship QVAC + bare-runtime platform pkgs
return [...QVAC_NATIVE_HOSTS]
}
if (hosts?.length) return hosts
if (process.env.PEARDATA_CLIENT_HOSTS) {
return parseHostList(process.env.PEARDATA_CLIENT_HOSTS, [...QVAC_NATIVE_HOSTS]).filter(
(h) => QVAC_NATIVE_HOSTS.includes(h) || ALL_64.includes(h)
)
}
// Default: every host that can run full QVAC
return [...QVAC_NATIVE_HOSTS]
}
if (require.main === module) {
ensure(parseArgs(process.argv.slice(2)))
}
module.exports = {
ensure,
hasPlatformPackage,
bareBinaryPath,
readBareRuntimeVersion,
QVAC_NATIVE_HOSTS,
}
+19
View File
@@ -90,10 +90,29 @@ function predownloadElectron(hosts) {
}) })
} }
function ensureBareRuntimes(hosts) {
if (process.env.PEARDATA_SKIP_QVAC === '1' || process.env.PEARDATA_SKIP_QVAC === 'true') {
console.log('[make] skip bare-runtime ensure (PEARDATA_SKIP_QVAC=1)')
return
}
// Only QVAC-native hosts need the platform bare binary in the client package
const need = hosts.filter((h) => hostSupportsQvacNative(h))
if (!need.length) return
console.log(`[make] ensuring bare-runtime platform packages: ${need.join(', ')}`)
run(process.execPath, [
path.join(root, 'scripts', 'ensure-qvac-bare-runtimes.cjs'),
'--hosts',
need.join(','),
])
}
function makeClient(hosts = CLIENT_HOSTS) { function makeClient(hosts = CLIENT_HOSTS) {
console.log(`[make] client hosts: ${hosts.join(', ')}`) console.log(`[make] client hosts: ${hosts.join(', ')}`)
npmRun('build:client-bundle') npmRun('build:client-bundle')
predownloadElectron(hosts) predownloadElectron(hosts)
// Cross-host CI: npm ci never installs darwin/win32 optional bare-runtime-* packages.
// Install them before forge packages so the app ships the Bare worker binary.
ensureBareRuntimes(hosts)
for (const host of hosts) { for (const host of hosts) {
const script = clientNpmScript(host) const script = clientNpmScript(host)