#!/usr/bin/env node import { spawnSync } from 'node:child_process'; import { existsSync } from 'node:fs'; import { resolveNatsServer, readNatsServerVersion } from './lib/nats-server-tool.mjs'; const json = process.argv.includes('--json'); const mode = process.argv.includes('--standalone') ? 'standalone' : process.argv.includes('--hivecast') ? 'hivecast' : process.argv.includes('--all') ? 'all' : 'base'; function check(name, ok, details = {}) { return { name, ok, ...details }; } function run(command, args) { const result = spawnSync(command, args, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }); if (result.error) return { ok: false, error: result.error.message }; if (result.status !== 0) return { ok: false, error: `${command} exited ${result.status}`, stderr: result.stderr.trim() }; return { ok: true, stdout: result.stdout.trim(), stderr: result.stderr.trim() }; } function major(version) { return Number(String(version).split('.')[0] ?? 0); } function findBrowser() { if (process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE && existsSync(process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE)) { return { path: process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE, source: 'PLAYWRIGHT_CHROMIUM_EXECUTABLE' }; } for (const path of ['/usr/bin/google-chrome', '/usr/bin/google-chrome-stable', '/usr/bin/chromium', '/usr/bin/chromium-browser']) { if (existsSync(path)) return { path, source: 'system' }; } return null; } const checks = []; const nodeVersion = process.versions.node; checks.push(check('node', major(nodeVersion) >= 22, { version: nodeVersion, required: '22+', fix: 'Install Node.js 22 or newer.', })); const pnpm = run('pnpm', ['--version']); checks.push(check('pnpm', pnpm.ok && major(pnpm.stdout) >= 10, { version: pnpm.ok ? pnpm.stdout : null, required: '10+', fix: 'Install pnpm 10: corepack enable && corepack prepare pnpm@10.29.3 --activate', error: pnpm.ok ? undefined : pnpm.error, })); let nats = null; try { nats = resolveNatsServer(); checks.push(check('nats-server', Boolean(nats), { path: nats?.path ?? null, source: nats?.source ?? null, version: nats ? readNatsServerVersion(nats.path) : null, requiredFor: 'pnpm demo:standalone', fix: 'Run pnpm setup:nats, or install NATS Server and put nats-server on PATH.', })); } catch (error) { checks.push(check('nats-server', false, { error: error instanceof Error ? error.message : String(error), requiredFor: 'pnpm demo:standalone', fix: 'Run pnpm setup:nats, or set MATRIX_NATS_SERVER to the nats-server binary.', })); } const browser = findBrowser(); checks.push(check('chromium', Boolean(browser), { path: browser?.path ?? null, source: browser?.source ?? null, requiredFor: 'live browser proof in pnpm prove:fresh-samples', fix: 'Run pnpm setup:browser, or set PLAYWRIGHT_CHROMIUM_EXECUTABLE to Chrome/Chromium.', })); const hivecastEnv = { MATRIX_CLOUD: Boolean(process.env.MATRIX_CLOUD), MATRIX_SPACE: Boolean(process.env.MATRIX_SPACE), MATRIX_API_KEY: Boolean(process.env.MATRIX_API_KEY), }; checks.push(check('hivecast-env', Object.values(hivecastEnv).every(Boolean), { present: hivecastEnv, requiredFor: 'live HiveCast server/browser samples in pnpm prove:fresh-samples', fix: 'Create a HiveCast API key, then export MATRIX_CLOUD, MATRIX_SPACE, and MATRIX_API_KEY.', })); const requiredByMode = { base: ['node', 'pnpm'], standalone: ['node', 'pnpm', 'nats-server'], hivecast: ['node', 'pnpm', 'chromium', 'hivecast-env'], all: ['node', 'pnpm', 'nats-server', 'chromium', 'hivecast-env'], }; const requiredFailures = checks.filter((item) => requiredByMode[mode].includes(item.name) && !item.ok); const proof = { ok: requiredFailures.length === 0, mode, checks, }; if (json) { console.log(JSON.stringify(proof, null, 2)); } else { console.log(`Matrix SDK doctor mode: ${mode}`); for (const item of checks) { const required = requiredByMode[mode].includes(item.name); const mark = item.ok ? 'ok' : required ? 'missing' : 'optional-missing'; console.log(`${mark} ${item.name}${item.version ? ` (${item.version})` : ''}${item.path ? ` at ${item.path}` : ''}`); if (!item.ok) console.log(` fix: ${item.fix}`); } } process.exit(proof.ok ? 0 : 1);