refactor(main): execFileAsync helper — unify the 5 spawn-and-read sites

Replace the hand-rolled `new Promise((resolve) => execFile(…, cb))` wrappers in
probe / ytdlp (×2) / ffmpeg / indexer.probeFlat / download.probeMeta with one
lib/exec.ts execFileAsync returning a normalized
{ok,stdout,stderr,timedOut,error} that never rejects. Each caller keeps its own
error mapping (timedOut -> "Timed out …" vs stderr) and JSON parsing; behaviour
is unchanged. windowsHide defaults on; per-call timeout/maxBuffer preserved.

Foundation for CC4/CC5 — the stdout line-buffer half lands with SIMP5, the
net.request half with SIMP16, at which point those checkboxes close.

Unit-tested (exec.test.ts) against the node binary: ok / stderr+nonzero-exit /
missing-binary / timeout. typecheck + 262 tests + eslint + prettier green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-01 09:06:08 -04:00
parent 66eaaac8fc
commit 3baba3b7bc
7 changed files with 192 additions and 151 deletions
+41
View File
@@ -0,0 +1,41 @@
import { describe, it, expect } from 'vitest'
import { execFileAsync } from '../src/main/lib/exec'
// Exercised against the Node binary itself so the test needs no fixtures and is
// deterministic. Covers the contract the six migrated spawn sites rely on
// (SIMP2/CC4/CC5): normalized ok/stdout/stderr, never-rejects, and the timedOut flag.
describe('execFileAsync (SIMP2/CC4/CC5)', () => {
it('resolves ok with stdout on a clean exit', async () => {
const r = await execFileAsync(process.execPath, ['-e', 'process.stdout.write("hello")'])
expect(r.ok).toBe(true)
expect(r.stdout).toBe('hello')
expect(r.timedOut).toBe(false)
expect(r.error).toBeUndefined()
})
it('captures stderr and reports not-ok on a non-zero exit', async () => {
const r = await execFileAsync(process.execPath, [
'-e',
'process.stderr.write("boom"); process.exit(2)'
])
expect(r.ok).toBe(false)
expect(r.stderr).toContain('boom')
expect(r.error).toBeInstanceOf(Error)
expect(r.timedOut).toBe(false)
})
it('never rejects — a missing binary resolves not-ok with an error', async () => {
const r = await execFileAsync('definitely-not-a-real-binary-xyz', ['--version'])
expect(r.ok).toBe(false)
expect(r.error).toBeDefined()
expect(r.timedOut).toBe(false)
})
it('flags timedOut when the timeout kills the process', async () => {
const r = await execFileAsync(process.execPath, ['-e', 'setTimeout(() => {}, 5000)'], {
timeout: 100
})
expect(r.ok).toBe(false)
expect(r.timedOut).toBe(true)
})
})