import { tryLoadConfig } from '../config.js'; import { existsSync } from 'fs'; export interface DoctorOptions { cwd?: string; } /** * Diagnose common setup issues. */ export async function doctorCommand(opts: DoctorOptions = {}): Promise { let ok = true; console.log('\x1b[33mShade doctor\x1b[0m\n'); // 1. Config loadable? const configResult = tryLoadConfig(opts.cwd); if (configResult.ok) { console.log(' \x1b[32m✓\x1b[0m Config loaded from .shaderc.json or env vars'); const config = configResult.config; console.log(` prekeyServer: ${config.prekeyServer}`); console.log(` storage: ${config.storage}`); // 2. Storage path accessible? if (config.storage.startsWith('sqlite:')) { const path = config.storage.slice('sqlite:'.length); const dir = path.substring(0, path.lastIndexOf('/')) || '.'; if (existsSync(dir)) { console.log(` \x1b[32m✓\x1b[0m Storage directory exists: ${dir}`); } else { console.log(` \x1b[31m✗\x1b[0m Storage directory missing: ${dir}`); ok = false; } } // 3. Prekey server reachable? try { const res = await fetch(`${config.prekeyServer}/health`, { signal: AbortSignal.timeout(5000), }); if (res.ok) { console.log(` \x1b[32m✓\x1b[0m Prekey server is reachable`); } else { console.log(` \x1b[31m✗\x1b[0m Prekey server returned HTTP ${res.status}`); ok = false; } } catch (err) { console.log(` \x1b[31m✗\x1b[0m Cannot reach prekey server: ${(err as Error).message}`); ok = false; } // 4. Observer token set? if (config.observerToken) { if (config.observerToken.length >= 16) { console.log(` \x1b[32m✓\x1b[0m Observer token is set and long enough`); } else { console.log(` \x1b[31m✗\x1b[0m Observer token must be at least 16 characters`); ok = false; } } else { console.log(` \x1b[90m○\x1b[0m Observer token not set (dashboard disabled)`); } } else { console.log(` \x1b[31m✗\x1b[0m ${configResult.error}`); ok = false; } console.log(); if (ok) { console.log('\x1b[32mAll checks passed.\x1b[0m'); } else { console.log('\x1b[31mSome checks failed. Fix the issues above and re-run.\x1b[0m'); process.exitCode = 1; } }