/** * Fail-closed QVAC addon packing: after bare-pack, every required .bare must * be in the bundle and binding.js must resolve require.addon() to it. * * Plugin JS imports do not load natives (except diffusion/llm-style * addonLogging). The pack graph edge to the .bare is binding.js. */ const fs = require('fs'); const path = require('path'); const packed = require('../native-host/qvac/packed-plugins.js'); const ROOT = path.join(__dirname, '..'); const DEFAULT_NATIVE_HOST_DIR = path.join(ROOT, 'native-host'); function addonBareName(pkg) { return String(pkg || '') .replace(/^@/, '') .replace(/\//g, '__'); } function packedAddonPackages() { return ['@qvac/llm-llamacpp'].concat(packed.uniqueAddonPackages()); } function hostParts(host) { const s = String(host || ''); const i = s.indexOf('-'); if (i <= 0) return { platform: s, arch: '' }; return { platform: s.slice(0, i), arch: s.slice(i + 1) }; } function guessHostFromKey(key) { const n = String(key || '').replace(/\\/g, '/'); const m = n.match(/prebuilds\/([^/]+)\//); return m ? m[1] : null; } function guessArchFromKey(key) { const host = guessHostFromKey(key); return host ? hostParts(host).arch : ''; } function bindingDotForHosts(hosts, keyForHost) { const list = hosts && hosts.length ? hosts : []; if (list.length === 1) return keyForHost(list[0]); const byPlat = {}; for (let i = 0; i < list.length; i++) { const host = list[i]; const { platform, arch } = hostParts(host); if (!byPlat[platform]) byPlat[platform] = {}; byPlat[platform][arch || 'x64'] = keyForHost(host); } const out = {}; const plats = Object.keys(byPlat); for (let i = 0; i < plats.length; i++) { const plat = plats[i]; const arches = Object.keys(byPlat[plat]); out[plat] = arches.length === 1 ? byPlat[plat][arches[0]] : byPlat[plat]; } return out; } function bundleKeyPrefix(bundle) { if (typeof bundle.keys !== 'function') return '/'; for (const key of bundle.keys()) { const s = String(key); if (s.indexOf('node_modules') !== -1) return s.charAt(0) === '/' ? '/' : ''; } return '/'; } function makeKey(prefix, posixRel) { const n = String(posixRel || '') .replace(/\\/g, '/') .replace(/^\/+/, ''); return prefix + n; } function findKey(bundle, posixRel) { const n = String(posixRel || '') .replace(/\\/g, '/') .replace(/^\/+/, ''); const candidates = ['/' + n, n]; for (let i = 0; i < candidates.length; i++) { if (bundle.exists(candidates[i])) return candidates[i]; } if (typeof bundle.keys !== 'function') return null; for (const key of bundle.keys()) { const kn = String(key) .replace(/\\/g, '/') .replace(/^\/+/, ''); if (kn === n || kn.endsWith('/' + n)) return key; } return null; } function putFile(bundle, key, data, opts) { const existed = bundle.exists(key); const alreadyAddon = Array.isArray(bundle.addons) && bundle.addons.indexOf(key) !== -1; const wantAddon = !!(opts && opts.addon); bundle.write(key, data, { addon: wantAddon && !alreadyAddon && !existed, }); return existed ? 'updated' : 'added'; } function posixKey(key) { return String(key || '').replace(/\\/g, '/'); } const KNOWN_PREBUILD_HOST = /^(darwin|linux|win32|android|ios)-/; /** C headers, CMake, CUDA/HIP/ROCm, debug artifacts under prebuilds/ — not needed at runtime. */ function isNonRuntimePrebuildPath(rel) { const n = posixKey(rel); const lower = n.toLowerCase(); if (!/prebuilds(\/|$)/.test(lower)) return false; if (/(^|\/)include\//.test(lower) || /(^|\/)share\//.test(lower)) return true; if (/(^|\/)(include|share)$/.test(lower)) return true; if (/\.(h|hpp|hh|hxx|c|cc|cpp|cxx|cmake|pdb|lib|exp)$/i.test(n)) return true; if (/\.dsym(\/|$)/i.test(lower)) return true; if (/(^|[/\-_])(cuda|cudart|cublas|hip|rocm|opencl)([/\-_.]|$)/i.test(n)) return true; return false; } function shouldPruneBundleKey(key, hosts) { const n = posixKey(key); const host = guessHostFromKey(n); const allowed = hosts && hosts.length ? hosts : []; if (host && KNOWN_PREBUILD_HOST.test(host) && allowed.indexOf(host) === -1) return true; if (isNonRuntimePrebuildPath(n)) return true; return false; } function removeBundleKey(bundle, key) { if (bundle._files && typeof bundle._files.delete === 'function') bundle._files.delete(key); if (Array.isArray(bundle._addons)) { const i = bundle._addons.indexOf(key); if (i !== -1) bundle._addons.splice(i, 1); } if (Array.isArray(bundle._assets)) { const i = bundle._assets.indexOf(key); if (i !== -1) bundle._assets.splice(i, 1); } if (bundle._resolutions && Object.prototype.hasOwnProperty.call(bundle._resolutions, key)) { delete bundle._resolutions[key]; } } function prunePackedWaste(bundle, hosts) { const dropped = []; if (!bundle || typeof bundle.keys !== 'function') return dropped; const keys = [...bundle.keys()]; for (let i = 0; i < keys.length; i++) { const key = keys[i]; if (!shouldPruneBundleKey(key, hosts)) continue; removeBundleKey(bundle, key); dropped.push(key); } return dropped; } function logBundleInventory(bundle, limit) { const n = limit == null ? 20 : limit; if (!bundle || typeof bundle.keys !== 'function') return []; const entries = []; let total = 0; for (const key of bundle.keys()) { const size = typeof bundle.size === 'function' ? bundle.size(key) : 0; total += size; entries.push({ key, size }); } entries.sort((a, b) => b.size - a.size); console.log( ' Bundle inventory: ' + entries.length + ' files, ' + (total / 1024 / 1024).toFixed(1) + ' MB (top ' + Math.min(n, entries.length) + ')' ); for (let i = 0; i < entries.length && i < n; i++) { const e = entries[i]; console.log(' ' + (e.size / 1024 / 1024).toFixed(1) + ' MB ' + e.key); } return entries; } function walkDiskFiles(dir, relPrefix, out, io) { const exists = io && io.exists ? io.exists : (p) => fs.existsSync(p); const readdir = io && io.readdir ? io.readdir : (p) => fs.readdirSync(p); const stat = io && io.stat ? io.stat : (p) => fs.statSync(p); if (!exists(dir)) return; let names; try { names = readdir(dir); } catch (_) { return; } for (let i = 0; i < names.length; i++) { const name = names[i]; const abs = path.join(dir, name); const rel = relPrefix + '/' + name; if (isNonRuntimePrebuildPath(rel) || /^(include|share)$/i.test(name)) continue; let st; try { st = stat(abs); } catch (_) { continue; } if (st && st.isDirectory()) walkDiskFiles(abs, rel, out, io); else out.push({ abs, rel }); } } function setBindingResolution(bundle, bindingKey, hosts, keyForHost) { const current = Object.assign({}, (bundle.resolutions && bundle.resolutions[bindingKey]) || {}); const pkgRel = bindingKey.replace(/binding\.js$/, 'package.json').replace(/^\/+/, ''); const pkgKey = findKey(bundle, pkgRel) || makeKey(bindingKey.charAt(0) === '/' ? '/' : '', pkgRel); if (!current['#package']) current['#package'] = pkgKey; current['.'] = bindingDotForHosts(hosts, keyForHost); bundle.resolutions[bindingKey] = current; } function ensurePackedQvacAddons(bundle, opts) { const options = opts || {}; const hosts = options.hosts; if (!hosts || !hosts.length) { throw new Error('ensurePackedQvacAddons: hosts required'); } const nativeHostDir = options.nativeHostDir || DEFAULT_NATIVE_HOST_DIR; const packages = options.addonPackages || packedAddonPackages(); const readFile = options.readFile || ((p) => fs.readFileSync(p)); const exists = options.exists || ((p) => fs.existsSync(p)); const io = { exists, readdir: options.readdir || ((p) => fs.readdirSync(p)), stat: options.stat || ((p) => fs.statSync(p)), }; const prefix = bundleKeyPrefix(bundle); const injected = []; const missing = []; for (let p = 0; p < packages.length; p++) { const pkg = packages[p]; const bareName = addonBareName(pkg); const pkgDir = path.join(nativeHostDir, 'node_modules', pkg); const jsFiles = ['package.json', 'index.js', 'binding.js', 'addonLogging.js', 'addon.js']; for (let j = 0; j < jsFiles.length; j++) { const name = jsFiles[j]; const abs = path.join(pkgDir, name); if (!exists(abs)) continue; const rel = 'node_modules/' + pkg + '/' + name; const key = findKey(bundle, rel) || makeKey(prefix, rel); if (bundle.exists(key) && bundle.size(key) > 0) continue; putFile(bundle, key, readFile(abs), { addon: false }); injected.push(key); } const keyForHost = function (host) { return makeKey(prefix, 'node_modules/' + pkg + '/prebuilds/' + host + '/' + bareName + '.bare'); }; for (let h = 0; h < hosts.length; h++) { const host = hosts[h]; const preRel = 'node_modules/' + pkg + '/prebuilds/' + host; const preDir = path.join(pkgDir, 'prebuilds', host); const bareAbs = path.join(preDir, bareName + '.bare'); if (!exists(bareAbs)) { missing.push(pkg + ' prebuild missing on disk for ' + host + ' (' + bareAbs + ')'); continue; } const files = []; walkDiskFiles(preDir, preRel, files, io); if (!files.length) { files.push({ abs: bareAbs, rel: preRel + '/' + bareName + '.bare' }); } for (let f = 0; f < files.length; f++) { const rel = files[f].rel.replace(/\\/g, '/'); const key = findKey(bundle, rel) || makeKey(prefix, rel); const isBare = /\.bare$/i.test(rel); if (isNonRuntimePrebuildPath(rel)) continue; if (bundle.exists(key) && bundle.size(key) > 0) continue; putFile(bundle, key, readFile(files[f].abs), { addon: true }); injected.push(key); if (isBare) { console.log(' Injected QVAC addon ' + key); } } const bareKey = findKey(bundle, preRel + '/' + bareName + '.bare') || keyForHost(host); if (!bundle.exists(bareKey) || bundle.size(bareKey) <= 0) { missing.push(pkg + ' .bare not in bundle for ' + host + ' (' + bareKey + ')'); } } const bindingRel = 'node_modules/' + pkg + '/binding.js'; const bindingKey = findKey(bundle, bindingRel) || makeKey(prefix, bindingRel); if (bundle.exists(bindingKey)) { setBindingResolution(bundle, bindingKey, hosts, function (host) { return findKey(bundle, 'node_modules/' + pkg + '/prebuilds/' + host + '/' + bareName + '.bare') || keyForHost(host); }); } else { missing.push(pkg + ' binding.js not in bundle'); } } if (missing.length) { throw new Error('Packed host is missing QVAC addons:\n ' + missing.join('\n ')); } if (injected.length) { console.log(' Ensured ' + injected.length + ' QVAC addon file(s) in bundle'); } else { console.log(' QVAC addon .bare files present for ' + hosts.join(', ')); } return { injected }; } module.exports = { addonBareName, packedAddonPackages, hostParts, guessHostFromKey, guessArchFromKey, bindingDotForHosts, bundleKeyPrefix, makeKey, findKey, isNonRuntimePrebuildPath, shouldPruneBundleKey, prunePackedWaste, logBundleInventory, ensurePackedQvacAddons, };