mirror of
https://github.com/Orkas-AI/Orkas.git
synced 2026-11-07 06:16:03 -10:00
* fix: verify native dependencies in Electron and support repair * ci: capture native child diagnostics on repair failures * fix: preserve known Linux sharp warnings and stable Windows ESM tests * Isolate application sharp operations in bundled Node * Test isolated SVG text rendering without native diagnostics * Keep CI system fonts read-only and reject GUI GLib collisions --------- Co-authored-by: CloudTianTian <[email protected]>
114 lines
5.9 KiB
JavaScript
114 lines
5.9 KiB
JavaScript
#!/usr/bin/env node
|
|
'use strict';
|
|
|
|
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
const { createRequire } = require('node:module');
|
|
const { spawnSync } = require('node:child_process');
|
|
const { probeSqlite } = require('./native-dependency-probe.cjs');
|
|
|
|
const MINIMUM_NODE_VERSION = '22.12.0';
|
|
const PROBE_TIMEOUT_MS = 60_000;
|
|
const root = path.resolve(__dirname, '..');
|
|
|
|
function verifySharpRuntime({ packageRoot = root, spawn = spawnSync, env = process.env } = {}) {
|
|
let nodeExecutable;
|
|
let nodeVersion;
|
|
try {
|
|
const { verifyRuntimeDir } = require('../bin/runtime-gate.cjs');
|
|
const runtimeRoot = path.join(packageRoot, 'resources', 'runtime');
|
|
const manifest = JSON.parse(fs.readFileSync(path.join(runtimeRoot, 'manifest.json'), 'utf8'));
|
|
const key = `${process.platform}-${process.arch}`;
|
|
nodeVersion = manifest.node.version;
|
|
nodeExecutable = verifyRuntimeDir('node', path.join(runtimeRoot, 'node', key), key,
|
|
manifest.node, manifest.node.assets[key], process.platform, process.arch);
|
|
} catch {
|
|
throw new Error('The bundled Node image runtime is missing or invalid; run npm run native:repair.');
|
|
}
|
|
const { probeImageRuntime } = require('../bin/sharp-worker.cjs');
|
|
const record = probeImageRuntime(nodeExecutable, path.join(packageRoot, 'bin', 'sharp-worker.cjs'), spawn, env);
|
|
if (record.node !== nodeVersion || record.platform !== process.platform || record.arch !== process.arch) {
|
|
throw new Error('sharp failed verification in the bundled Node image runtime; run npm run native:repair.');
|
|
}
|
|
return record;
|
|
}
|
|
|
|
// Built-ins only: this preflight also runs before node_modules exists.
|
|
function assertBootstrapNode(version = process.versions.node) {
|
|
const parts = String(version).split('.');
|
|
const valid = parts.length === 3 && parts.every(part => /^\d+$/.test(part));
|
|
if (!valid || Number(parts[0]) < 22 || (Number(parts[0]) === 22 && Number(parts[1]) < 12)) {
|
|
throw new Error(`Node.js ${MINIMUM_NODE_VERSION}+ is required to prepare native dependencies; use Node.js 24 LTS and retry.`);
|
|
}
|
|
}
|
|
|
|
function verifyNativeDependencies({ packageRoot = root, spawn = spawnSync, env = process.env } = {}) {
|
|
assertBootstrapNode();
|
|
const requirePackage = createRequire(path.join(packageRoot, 'package.json'));
|
|
let lock;
|
|
try { lock = JSON.parse(fs.readFileSync(path.join(packageRoot, 'package-lock.json'), 'utf8')); }
|
|
catch { throw new Error('Cannot read package-lock.json; restore the checkout lockfile, then run npm run native:repair.'); }
|
|
for (const name of ['electron', 'better-sqlite3', 'sqlite-vec', 'sharp']) {
|
|
const expected = lock.packages?.[`node_modules/${name}`]?.version;
|
|
let installed;
|
|
try { installed = JSON.parse(fs.readFileSync(path.join(packageRoot, 'node_modules', name, 'package.json'), 'utf8')).version; }
|
|
catch { /* handled as an incomplete installation below */ }
|
|
if (!expected || installed !== expected) {
|
|
throw new Error(`${name} does not match package-lock.json; run npm run native:repair in this checkout.`);
|
|
}
|
|
}
|
|
const electronVersion = lock.packages['node_modules/electron'].version;
|
|
let electronPath;
|
|
try { electronPath = requirePackage('electron'); }
|
|
catch { throw new Error('Electron is not installed correctly; run npm run native:repair in this checkout.'); }
|
|
// Use the target Electron's require, never this system Node's addon loader.
|
|
const script = `
|
|
const { createRequire } = require('node:module');
|
|
const requirePackage = createRequire(${JSON.stringify(path.join(packageRoot, 'package.json'))});
|
|
(async () => {
|
|
const sqlite = await (${probeSqlite.toString()})(requirePackage);
|
|
console.log(JSON.stringify({status:'passed', ...sqlite,
|
|
platform:process.platform, arch:process.arch, electron:process.versions.electron,
|
|
node:process.versions.node, modules:process.versions.modules, napi:process.versions.napi}));
|
|
})().catch(error => { console.error(error.message); process.exitCode = 1; });
|
|
`;
|
|
const result = spawn(electronPath, ['-e', script], {
|
|
cwd: packageRoot, env: { ...env, ELECTRON_RUN_AS_NODE: '1' },
|
|
encoding: 'utf8', timeout: PROBE_TIMEOUT_MS, maxBuffer: 1024 * 1024,
|
|
windowsHide: true,
|
|
});
|
|
let record;
|
|
try { record = JSON.parse(String(result.stdout || '').trim()); } catch { /* fail closed */ }
|
|
if (result.error || result.status !== 0 || String(result.stderr || '').trim()
|
|
|| record?.status !== 'passed' || record.electron !== electronVersion
|
|
|| record.platform !== process.platform || record.arch !== process.arch
|
|
|| typeof record.sqliteVec !== 'string' || !record.sqliteVec
|
|
|| record.vectorDistance !== 5) {
|
|
// Only relay the bounded messages authored by our probe. Native stderr can
|
|
// contain private paths; unknown loader failures keep the recovery context.
|
|
const known = ['better-sqlite3', 'sqlite-vec', 'sharp'].find(name =>
|
|
String(result.stderr || '').startsWith(`${name} could not complete`));
|
|
const reason = result.error?.code === 'ETIMEDOUT' ? 'timed out' : 'failed';
|
|
throw new Error(`Native dependency verification ${reason}${known ? ` (${known})` : ''} for Electron ${electronVersion} on ${process.platform}-${process.arch}. Run npm run native:repair; check optional packages, the target architecture and system shared libraries.`);
|
|
}
|
|
const imageRuntime = verifySharpRuntime({ packageRoot, spawn, env });
|
|
return { ...record, sharp: imageRuntime.sharp, imageRuntime };
|
|
}
|
|
|
|
if (require.main === module) {
|
|
try {
|
|
const args = process.argv.slice(2);
|
|
if (args.length > 1 || (args.length === 1 && args[0] !== '--host-only')) throw new Error('Use --host-only or no arguments.');
|
|
assertBootstrapNode();
|
|
if (process.argv[2] !== '--host-only') {
|
|
const result = verifyNativeDependencies();
|
|
console.log(JSON.stringify(result));
|
|
}
|
|
} catch (error) {
|
|
console.error(`[native-deps] ${error.message}`);
|
|
process.exitCode = 1;
|
|
}
|
|
}
|
|
|
|
module.exports = { MINIMUM_NODE_VERSION, PROBE_TIMEOUT_MS, assertBootstrapNode, verifySharpRuntime, verifyNativeDependencies };
|