Files
SubMiner/launcher/commands/doctor-command.ts
sudacode c749430c77 refactor(launcher): split CLI flow into command modules
Isolate process-side effects behind adapter seams and keep wrapper behavior stable while improving command-level testability.
2026-02-21 21:32:14 -08:00

86 lines
2.3 KiB
TypeScript

import fs from 'node:fs';
import { log } from '../log.js';
import { commandExists } from '../util.js';
import { resolveMainConfigPath } from '../config-path.js';
import type { LauncherCommandContext } from './context.js';
interface DoctorCommandDeps {
commandExists(command: string): boolean;
configExists(path: string): boolean;
resolveMainConfigPath(): string;
}
const defaultDeps: DoctorCommandDeps = {
commandExists,
configExists: fs.existsSync,
resolveMainConfigPath,
};
export function runDoctorCommand(
context: LauncherCommandContext,
deps: DoctorCommandDeps = defaultDeps,
): boolean {
const { args, appPath, mpvSocketPath, processAdapter } = context;
if (!args.doctor) {
return false;
}
const configPath = deps.resolveMainConfigPath();
const mpvFound = deps.commandExists('mpv');
const checks: Array<{ label: string; ok: boolean; detail: string }> = [
{
label: 'app binary',
ok: Boolean(appPath),
detail: appPath || 'not found (set SUBMINER_APPIMAGE_PATH)',
},
{
label: 'mpv',
ok: mpvFound,
detail: mpvFound ? 'found' : 'missing',
},
{
label: 'yt-dlp',
ok: deps.commandExists('yt-dlp'),
detail: deps.commandExists('yt-dlp') ? 'found' : 'missing (optional unless YouTube URLs)',
},
{
label: 'ffmpeg',
ok: deps.commandExists('ffmpeg'),
detail: deps.commandExists('ffmpeg')
? 'found'
: 'missing (optional unless subtitle generation)',
},
{
label: 'fzf',
ok: deps.commandExists('fzf'),
detail: deps.commandExists('fzf') ? 'found' : 'missing (optional if using rofi)',
},
{
label: 'rofi',
ok: deps.commandExists('rofi'),
detail: deps.commandExists('rofi') ? 'found' : 'missing (optional if using fzf)',
},
{
label: 'config',
ok: deps.configExists(configPath),
detail: configPath,
},
{
label: 'mpv socket path',
ok: true,
detail: mpvSocketPath,
},
];
const hasHardFailure = checks.some((entry) =>
entry.label === 'app binary' || entry.label === 'mpv' ? !entry.ok : false,
);
for (const check of checks) {
log(check.ok ? 'info' : 'warn', args.logLevel, `[doctor] ${check.label}: ${check.detail}`);
}
processAdapter.exit(hasHardFailure ? 1 : 0);
return true;
}