Fix QVAC Worker
CI / test (push) Successful in 2m40s
Release rolling / release (push) Successful in 11m42s

This commit is contained in:
Raven Scott
2026-07-30 16:01:34 -04:00
parent 726dbdfd56
commit 2007ff6ad3
5 changed files with 291 additions and 16 deletions
+3
View File
@@ -51,6 +51,9 @@ async function main() {
`[build-qvac-worker] ok addons=${(result.addons || []).length} bundle=${result.bundlePath || '(default)'}`
)
// bundleSdk writes absolute file:// imports — rewrite for packaged / CI clients
require('./rewrite-qvac-worker-entry.cjs').ensurePortableWorkerEntry({ root })
if (typeof commands.verifyBundle === 'function') {
const verify = await commands.verifyBundle({
projectRoot: root,
+197
View File
@@ -0,0 +1,197 @@
#!/usr/bin/env node
/**
* Make qvac/worker.entry.mjs portable for packaged Electron / CI clients.
*
* `@qvac/sdk` bundleSdk writes absolute `file:///…/node_modules/@qvac/…` imports
* so bare-pack can resolve addons on the *build* machine. Those paths break as
* soon as the app is shipped elsewhere → Bare worker dies →
* "RPC initialization timed out after 30000ms — the worker process may have failed to start"
*
* This rewrites those URLs to relative paths from qvac/worker.entry.mjs, e.g.
* ../node_modules/@qvac/sdk/dist/server/worker-core.js
*
* Usage:
* node scripts/rewrite-qvac-worker-entry.cjs
* node scripts/rewrite-qvac-worker-entry.cjs --entry /path/to/qvac/worker.entry.mjs
* node scripts/rewrite-qvac-worker-entry.cjs --root /path/to/app
*/
'use strict'
const fs = require('fs')
const path = require('path')
const { pathToFileURL, fileURLToPath } = require('url')
const rootDefault = path.resolve(__dirname, '..')
/**
* @param {string} entryPath absolute path to worker.entry.mjs
* @returns {{ rewritten: number, path: string, ok: boolean, reason?: string }}
*/
function rewriteWorkerEntry(entryPath) {
if (!fs.existsSync(entryPath)) {
return { rewritten: 0, path: entryPath, ok: false, reason: 'missing' }
}
const original = fs.readFileSync(entryPath, 'utf8')
const entryDir = path.dirname(entryPath)
let count = 0
const next = original.replace(/file:\/\/\/[^\s"'`]+|file:\/\/[^\s"'`]+/g, (href) => {
try {
let filePath
try {
filePath = fileURLToPath(href)
} catch {
return href
}
if (!fs.existsSync(filePath)) {
// Still rewrite if it points under a known node_modules layout
// (CI may rewrite before files are fully staged)
}
let rel = path.relative(entryDir, filePath)
if (!rel || rel === path.basename(filePath)) {
// same dir
rel = `./${path.basename(filePath)}`
} else if (!rel.startsWith('.') && !path.isAbsolute(rel)) {
rel = `./${rel}`
}
// ESM wants POSIX separators
rel = rel.split(path.sep).join('/')
// Prefer bare relative specifier (no file://) — Bare resolves these
count++
return rel
} catch {
return href
}
})
if (count === 0 && !/file:\/\//.test(original)) {
// Already portable — ensure relative @qvac paths exist or write canonical entry
if (
original.includes('../node_modules/@qvac/sdk/') ||
original.includes('@qvac/sdk/')
) {
return { rewritten: 0, path: entryPath, ok: true, reason: 'already-portable' }
}
}
if (next !== original) {
fs.writeFileSync(entryPath, next, 'utf8')
}
// Guard: no absolute build-machine paths left
if (/file:\/\/\/Users\/|file:\/\/\/home\/|file:\/\/\/[A-Za-z]:\//.test(next)) {
return {
rewritten: count,
path: entryPath,
ok: false,
reason: 'absolute file:// paths remain after rewrite',
}
}
return { rewritten: count, path: entryPath, ok: true }
}
/**
* Write a known-good portable LLM-only worker entry (matches qvac.config.json).
* Used when rewrite fails or entry is missing.
* @param {string} entryPath
*/
function writePortableLlmWorkerEntry(entryPath) {
const content = `/**
* QVAC SDK Worker Entry (portable)
* Relative imports only — safe for packaged Electron / CI clients.
* Plugins: 1 — @qvac/sdk/llamacpp-completion/plugin
*
* Regenerated by scripts/rewrite-qvac-worker-entry.cjs
* (bundleSdk emits absolute file:// URLs that break off the build machine)
*/
import { initializeWorkerCore, ensureRPCSetup } from '../node_modules/@qvac/sdk/dist/server/worker-core.js'
import { registerPlugin } from '../node_modules/@qvac/sdk/dist/server/plugins/index.js'
import { getServerLogger } from '../node_modules/@qvac/sdk/dist/logging/index.js'
import { llmPlugin } from '../node_modules/@qvac/sdk/dist/server/bare/plugins/llamacpp-completion/plugin.js'
const { hasRPCConfig } = initializeWorkerCore()
const logger = getServerLogger()
logger.info('🐻 QVAC Worker (portable bundle)')
logger.info('📦 Plugins: 1 (llamacpp-completion)')
registerPlugin(llmPlugin)
if (hasRPCConfig) {
ensureRPCSetup()
}
`
fs.mkdirSync(path.dirname(entryPath), { recursive: true })
fs.writeFileSync(entryPath, content, 'utf8')
return entryPath
}
/**
* @param {{ root?: string, entry?: string, forcePortable?: boolean }} [opts]
*/
function ensurePortableWorkerEntry(opts = {}) {
const root = opts.root || rootDefault
const entryPath =
opts.entry || path.join(root, 'qvac', 'worker.entry.mjs')
if (opts.forcePortable || !fs.existsSync(entryPath)) {
writePortableLlmWorkerEntry(entryPath)
console.log(`[rewrite-qvac-worker] wrote portable entry: ${entryPath}`)
return { ok: true, path: entryPath, rewritten: -1, reason: 'wrote-portable' }
}
const result = rewriteWorkerEntry(entryPath)
if (!result.ok && result.reason !== 'missing') {
console.warn(
`[rewrite-qvac-worker] rewrite incomplete (${result.reason}) — writing portable LLM entry`
)
writePortableLlmWorkerEntry(entryPath)
return { ok: true, path: entryPath, rewritten: result.rewritten, reason: 'fallback-portable' }
}
if (result.reason === 'missing') {
writePortableLlmWorkerEntry(entryPath)
return { ok: true, path: entryPath, rewritten: -1, reason: 'wrote-portable' }
}
console.log(
`[rewrite-qvac-worker] ${entryPath}: rewritten=${result.rewritten} (${result.reason || 'ok'})`
)
return result
}
function parseArgs(argv) {
/** @type {{ root?: string, entry?: string, forcePortable?: boolean }} */
const opts = {}
for (let i = 0; i < argv.length; i++) {
const a = argv[i]
if (a === '--root' && argv[i + 1]) opts.root = path.resolve(argv[++i])
else if (a.startsWith('--root=')) opts.root = path.resolve(a.slice(7))
else if (a === '--entry' && argv[i + 1]) opts.entry = path.resolve(argv[++i])
else if (a.startsWith('--entry=')) opts.entry = path.resolve(a.slice(8))
else if (a === '--force-portable') opts.forcePortable = true
else if (a === '--help' || a === '-h') {
console.log(
'Usage: node scripts/rewrite-qvac-worker-entry.cjs [--root DIR] [--entry FILE] [--force-portable]'
)
process.exit(0)
}
}
return opts
}
if (require.main === module) {
const result = ensurePortableWorkerEntry(parseArgs(process.argv.slice(2)))
if (!result.ok) {
console.error('[rewrite-qvac-worker] FAILED', result)
process.exit(1)
}
}
module.exports = {
rewriteWorkerEntry,
writePortableLlmWorkerEntry,
ensurePortableWorkerEntry,
pathToFileURL,
}