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