Files
SubMiner/launcher/commands/doctor-command.ts
sudacode 5feed360ca feat: add app-owned YouTube subtitle flow with absPlayer-style parsing (#31)
* fix: harden preload argv parsing for popup windows

* fix: align youtube playback with shared overlay startup

* fix: unwrap mpv youtube streams for anki media mining

* docs: update docs for youtube subtitle and mining flow

* refactor: unify cli and runtime wiring for startup and youtube flow

* feat: update subtitle sidebar overlay behavior

* chore: add shared log-file source for diagnostics

* fix(ci): add changelog fragment for immersion changes

* fix: address CodeRabbit review feedback

* fix: persist canonical title from youtube metadata

* style: format stats library tab

* fix: address latest review feedback

* style: format stats library files

* test: stub launcher youtube deps in CI

* test: isolate launcher youtube flow deps

* test: stub launcher youtube deps in failing case

* test: force x11 backend in launcher ci harness

* test: address latest review feedback

* fix(launcher): preserve user YouTube ytdl raw options

* docs(backlog): update task tracking notes

* fix(immersion): special-case youtube media paths in runtime and tracking

* feat(stats): improve YouTube media metadata and picker key handling

* fix(ci): format stats media library hook

* fix: address latest CodeRabbit review items

* docs: update youtube release notes and docs

* feat: auto-load youtube subtitles before manual picker

* fix: restore app-owned youtube subtitle flow

* docs: update youtube playback docs and config copy

* refactor: remove legacy youtube launcher mode plumbing

* fix: refine youtube subtitle startup binding

* docs: clarify youtube subtitle startup behavior

* fix: address PR #31 latest review follow-ups

* fix: address PR #31 follow-up review comments

* test: harden youtube picker test harness

* udpate backlog

* fix: add timeout to youtube metadata probe

* docs: refresh youtube and stats docs

* update backlog

* update backlog

* chore: release v0.9.0
2026-03-24 00:01:24 -07:00

97 lines
2.7 KiB
TypeScript

import fs from 'node:fs';
import { log } from '../log.js';
import { runAppCommandWithInherit } from '../mpv.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;
runAppCommandWithInherit(appPath: string, appArgs: string[]): void;
}
const defaultDeps: DoctorCommandDeps = {
commandExists,
configExists: fs.existsSync,
resolveMainConfigPath,
runAppCommandWithInherit,
};
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 legacy subtitle fallback is enabled)',
},
{
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,
},
];
for (const check of checks) {
log(check.ok ? 'info' : 'warn', args.logLevel, `[doctor] ${check.label}: ${check.detail}`);
}
if (args.doctorRefreshKnownWords) {
if (!appPath) {
processAdapter.exit(1);
return true;
}
deps.runAppCommandWithInherit(appPath, ['--refresh-known-words']);
return true;
}
const hasHardFailure = checks.some((entry) =>
entry.label === 'app binary' || entry.label === 'mpv' ? !entry.ok : false,
);
processAdapter.exit(hasHardFailure ? 1 : 0);
return true;
}