refactor(main): drop pass-through main-deps builders (#306)

* refactor(main): drop mining/anki main-deps builders

* refactor(anilist): drop main-deps builders

* refactor(jellyfin): drop main-deps builders

* refactor(overlay): drop overlay and shortcut main-deps builders

* refactor(main): drop ipc, tray, and osd main-deps builders

* refactor(startup): drop startup main-deps builders

* test(scripts): ignore stray local requests in AUR download test

Also apply prettier to launcher/mpv.test.ts so format:check:src passes.

* fix(shortcuts): forward overlay numeric selection hooks through the composer

composeShortcutRuntimes only passed the digit callbacks, so the visible-overlay
selection added in #78 never ran from the multi-copy/mine-sentence shortcuts.

* fix(stats): skip mpv auto-connect when stats starts the immersion tracker

The immersion-startup main-deps builder never forwarded shouldAutoConnectMpv,
so background stats and stats CLI startup always connected to mpv. Drop the
builder and pass the deps straight to the handler.

* build(deps): bump follow-redirects override to 1.16.2

Resolves GHSA-mrwp-f3q8-3g9p (high), which fails the quality-gate
bun audit --audit-level high step.

* test(subtitles): cover the null-tokenization settle path in prefetch test

The mock ignored the value the test resolved, so the null case never ran.

* test(e2e): surface mpv crashes and install current mpv on macOS CI

The harness IPC client never handled socket close, so a crashed mpv left
commands pending until the 120s test timeout with an empty mpv.log. Reject
pending commands on close, write mpv's own log via --log-file, and refresh
Homebrew on macOS CI so the installed mpv build no longer depends on the
runner image.

* test(e2e): render mpv through cocoa-cb OpenGL on macOS

mpv 0.41.0_12 with libplacebo 7.387.0 segfaults in gpu-next's MoltenVK path
on GitHub's paravirtual macOS GPU on the first frame, so the E2E session
never started.

* test(e2e): name mpv-stdio.log in the mpv close diagnostic
This commit is contained in:
sudacode authored and GitHub committed 2026-10-31 00:50:33 -07:00
1 parent 0129e03c0f
commit 2a0e931325
84 files changed
+719 -2754

No files matched your search

+39 -4
View File
@@ -15,25 +15,38 @@ export type MpvClient = {
type MpvReply = { request_id?: number; error?: string; data?: unknown };
function videoOutputArgs(display: E2eDisplay): string[] {
// Xvfb has no GPU; the software X11 output always works there.
if (display.kind === 'xvfb') return ['--vo=x11'];
// gpu-next's Vulkan path (libplacebo over MoltenVK) segfaults on the first frame
// on GitHub's paravirtual macOS GPU with libplacebo 7.387. `--vo=libmpv` is mpv's
// cocoa-cb OpenGL output, which avoids it and still opens the window the overlay tracks.
if (process.platform === 'darwin') return ['--vo=libmpv'];
return [];
}
export async function startMpv(options: {
socketPath: string;
mediaPath: string;
subtitlePath: string;
/** mpv's own verbose log (`--log-file`); `--no-terminal` silences its console output. */
logPath: string;
/** Process stdout/stderr, which still catches crash output from mpv's libraries. */
stdioLogPath: string;
display: E2eDisplay;
}): Promise<ChildProcess> {
const log = fs.openSync(options.logPath, 'w');
const log = fs.openSync(options.stdioLogPath, 'w');
const child = spawn(
'mpv',
[
'--no-config',
'--no-terminal',
`--log-file=${options.logPath}`,
'--pause',
'--keep-open=yes',
'--ao=null',
`--geometry=${E2E_SCREEN.width}x${E2E_SCREEN.height}+0+0`,
// Xvfb has no GPU; the software X11 output always works there.
...(options.display.kind === 'xvfb' ? ['--vo=x11'] : []),
...videoOutputArgs(options.display),
`--input-ipc-server=${options.socketPath}`,
`--sub-file=${options.subtitlePath}`,
options.mediaPath,
@@ -58,13 +71,31 @@ function tryConnect(socketPath: string): Promise<net.Socket | null> {
});
}
export async function connectMpv(socketPath: string): Promise<MpvClient> {
/**
* Connects to mpv's JSON IPC socket. Once the socket closes (mpv exited or
* crashed), pending and later commands reject instead of hanging until the test
* timeout; `describeClose` adds context such as mpv's exit status.
*/
export async function connectMpv(
socketPath: string,
describeClose: () => string = () => 'mpv IPC connection closed',
): Promise<MpvClient> {
const socket = await waitUntil(() => tryConnect(socketPath), {
description: `mpv IPC socket ${socketPath}`,
});
const pending = new Map<number, (reply: MpvReply) => void>();
let nextRequestId = 1;
let buffer = '';
let closedError: Error | null = null;
const failPending = () => {
closedError ??= new Error(describeClose());
for (const settle of pending.values()) settle({ error: closedError.message });
pending.clear();
};
socket.on('close', failPending);
// 'close' always follows 'error'; listening here keeps the error from being thrown.
socket.on('error', () => {});
socket.setEncoding('utf8');
socket.on('data', (chunk: string) => {
@@ -83,6 +114,10 @@ export async function connectMpv(socketPath: string): Promise<MpvClient> {
return {
command: (...args) =>
new Promise((resolve, reject) => {
if (closedError) {
reject(new Error(`mpv ${JSON.stringify(args)} failed: ${closedError.message}`));
return;
}
const requestId = nextRequestId++;
pending.set(requestId, (reply) => {
if (reply.error === 'success') resolve(reply.data);
+12 -2
View File
@@ -265,15 +265,25 @@ export async function startE2eSession(options: E2eSessionOptions = {}): Promise<
settings.close();
await stopProcess(seeder.child);
const mpvLogPath = path.join(root, 'mpv.log');
const mpvStdioLogPath = path.join(root, 'mpv-stdio.log');
const mpvProcess = await startMpv({
socketPath: mpvSocketPath,
mediaPath,
subtitlePath,
logPath: path.join(root, 'mpv.log'),
logPath: mpvLogPath,
stdioLogPath: mpvStdioLogPath,
display,
});
teardown.push(() => stopProcess(mpvProcess));
const mpv = await connectMpv(mpvSocketPath);
const mpv = await connectMpv(mpvSocketPath, () => {
const { exitCode, signalCode } = mpvProcess;
const status =
exitCode === null && signalCode === null
? 'mpv IPC connection closed'
: `mpv exited (code ${String(exitCode)}, signal ${String(signalCode)})`;
return `${status}; see ${mpvLogPath} and ${mpvStdioLogPath}`;
});
teardown.push(mpv.close);
const app = await launchApp(['--start']);