32 lines
3.3 KiB
JavaScript
32 lines
3.3 KiB
JavaScript
import { spawn } from 'node:child_process';
|
|
|
|
const run = (file, args, timeoutMs = 6000) => new Promise((resolve, reject) => {
|
|
const child = spawn(file, args, { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
let stdout = '', stderr = '', settled = false;
|
|
const finish = (error) => { if (settled) return; settled = true; clearTimeout(timer); error ? reject(error) : resolve(stdout.trim()); };
|
|
const timer = setTimeout(() => { child.kill('SIGTERM'); finish(new Error(`${file} timed out`)); }, timeoutMs);
|
|
child.stdout?.on('data', chunk => { stdout = (stdout + chunk).slice(-64000); });
|
|
child.stderr?.on('data', chunk => { stderr = (stderr + chunk).slice(-2000); });
|
|
child.once('error', finish);
|
|
child.once('close', code => finish(code === 0 ? null : new Error(stderr.trim() || `${file} exited ${code}`)));
|
|
});
|
|
const portal = iface => run('gdbus', ['call', '--session', '--dest', 'org.freedesktop.portal.Desktop', '--object-path', '/org/freedesktop/portal/desktop', '--method', 'org.freedesktop.DBus.Properties.Get', `org.freedesktop.portal.${iface}`, 'version']);
|
|
const checks = [
|
|
['session', false, async () => { if (process.env.XDG_SESSION_TYPE !== 'wayland') throw new Error('a GNOME Wayland session is required for the primary backend'); return 'Wayland'; }],
|
|
['remote-desktop', false, () => portal('RemoteDesktop')],
|
|
['screencast', false, () => portal('ScreenCast')],
|
|
['pipewire', false, async () => { await run('pw-cli', ['info', '0']); return 'live server responds'; }],
|
|
['frame-pipeline', false, async () => { await run('python3', ['-c', 'import gi; gi.require_version("Gst", "1.0"); from gi.repository import Gst; from PIL import Image; Gst.init(None); assert all(Gst.ElementFactory.find(n) for n in ["pipewiresrc", "videoconvert", "appsink"]), "missing GStreamer capture elements"; assert Image.registered_extensions().get(".webp") == "WEBP", "Pillow WebP missing"']); return 'GStreamer capture elements and Pillow WebP available'; }],
|
|
['at-spi', false, async () => { await run('python3', ['-c', 'import gi; gi.require_version("Atspi", "2.0"); from gi.repository import Atspi; Atspi.init(); assert Atspi.get_desktop(0) is not None, "accessibility desktop unavailable"']); return 'live accessibility desktop responds'; }],
|
|
['libei', false, async () => { await run('python3', ['-c', 'import ctypes; lib=ctypes.CDLL("libei.so.1"); assert lib.ei_new_sender and lib.ei_setup_backend_fd']); return 'sender library loads'; }],
|
|
['shell-helper', false, async () => { await run('gdbus', ['call', '--session', '--dest', 'io.qvac.Jarvis.Shell', '--object-path', '/io/qvac/Jarvis/Shell', '--method', 'io.qvac.Jarvis.Shell.ListWindows']); return 'GNOME extension window service responds'; }],
|
|
['daemon', true, async () => { await run('gdbus', ['call', '--session', '--dest', 'io.qvac.Jarvis', '--object-path', '/io/qvac/Jarvis', '--method', 'io.qvac.Jarvis.Session.ComputerStatus']); return 'computer-use status responds'; }],
|
|
];
|
|
let failed = 0;
|
|
for (const [name, optional, check] of checks) {
|
|
try { console.log(`ok ${name}: ${await check()}${optional ? ' (optional)' : ''}`); }
|
|
catch (error) { if (!optional) failed++; console.log(`FAIL ${name}: ${error.message}${optional ? ' (optional)' : ''}`); }
|
|
}
|
|
console.log(failed ? `cu-doctor: ${failed} required checks failed` : 'cu-doctor: live prerequisites passed; input and capture still require a consented cu-smoke run');
|
|
process.exitCode = failed ? 1 : 0;
|