Updates
This commit is contained in:
@@ -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: |
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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,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
@@ -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,
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user