From d1f992570a2a2d7cb4fa0b4a7e31687a0791803d Mon Sep 17 00:00:00 2001 From: Wang Date: Mon, 20 Apr 2026 02:05:24 +0800 Subject: [PATCH] =?UTF-8?q?fix(cli):=20use=20'where'=20instead=20of=20'whi?= =?UTF-8?q?ch'=20on=20Windows=20for=20FFmpeg=20and=20br=E2=80=A6=20(#336)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(cli): use 'where' instead of 'which' on Windows for FFmpeg and browser detection - findFFmpeg() now uses 'where ffmpeg' on Windows, 'which ffmpeg' on Unix - whichBinary() now uses 'where' on Windows, 'which' on Unix Fixes FFmpeg detection failure on Windows where 'which' command doesn't exist. Co-Authored-By: Claude Opus 4.7 * fix(cli): handle multi-line output from Windows 'where' command Windows 'where' can return multiple paths (one per line) when there are multiple matches on PATH. Take only the first non-empty line. Co-Authored-By: Claude Opus 4.7 * fix(cli): extend Windows 'where' fix to whisper, tts, and clipboard modules - whisper/manager.ts: whichBinary() now uses 'where' on Windows - tts/synthesize.ts: findPython() now uses 'where' on Windows - utils/clipboard.ts: detectProvider() now uses 'where' on Windows All functions handle multi-line output from 'where' command. Co-Authored-By: Claude Opus 4.7 --------- Co-authored-by: Claude Opus 4.7 --- packages/cli/src/browser/ffmpeg.ts | 11 ++++++++--- packages/cli/src/browser/manager.ts | 11 ++++++++--- packages/cli/src/tts/synthesize.ts | 14 ++++++++++---- packages/cli/src/utils/clipboard.ts | 5 +++-- packages/cli/src/whisper/manager.ts | 11 ++++++++--- 5 files changed, 37 insertions(+), 15 deletions(-) diff --git a/packages/cli/src/browser/ffmpeg.ts b/packages/cli/src/browser/ffmpeg.ts index b9e1d2894..5dcaebc4f 100644 --- a/packages/cli/src/browser/ffmpeg.ts +++ b/packages/cli/src/browser/ffmpeg.ts @@ -2,12 +2,17 @@ import { execSync } from "node:child_process"; export function findFFmpeg(): string | undefined { try { - const result = execSync("which ffmpeg", { + const cmd = process.platform === "win32" ? "where ffmpeg" : "which ffmpeg"; + const output = execSync(cmd, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], timeout: 5000, - }).trim(); - return result || undefined; + }); + const first = output + .split(/\r?\n/) + .map((s) => s.trim()) + .find(Boolean); + return first || undefined; } catch { return undefined; } diff --git a/packages/cli/src/browser/manager.ts b/packages/cli/src/browser/manager.ts index 4ca5a4ab4..eaab48d95 100644 --- a/packages/cli/src/browser/manager.ts +++ b/packages/cli/src/browser/manager.ts @@ -38,12 +38,17 @@ const SYSTEM_CHROME_PATHS: ReadonlyArray = function whichBinary(name: string): string | undefined { try { - const result = execSync(`which ${name}`, { + const cmd = process.platform === "win32" ? `where ${name}` : `which ${name}`; + const output = execSync(cmd, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], timeout: 5000, - }).trim(); - return result || undefined; + }); + const first = output + .split(/\r?\n/) + .map((s) => s.trim()) + .find(Boolean); + return first || undefined; } catch { return undefined; } diff --git a/packages/cli/src/tts/synthesize.ts b/packages/cli/src/tts/synthesize.ts index 8f75cb1a8..e2a5984fa 100644 --- a/packages/cli/src/tts/synthesize.ts +++ b/packages/cli/src/tts/synthesize.ts @@ -11,20 +11,26 @@ import { ensureModel, ensureVoices, DEFAULT_VOICE } from "./manager.js"; function findPython(): string | undefined { for (const name of ["python3", "python"]) { try { - const result = execFileSync("which", [name], { + const cmd = process.platform === "win32" ? "where" : "which"; + const output = execFileSync(cmd, [name], { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], timeout: 5000, - }).trim(); + }); + const first = output + .split(/\r?\n/) + .map((s) => s.trim()) + .find(Boolean); + if (!first) continue; // Verify it's Python 3 - const version = execFileSync(result, ["--version"], { + const version = execFileSync(first, ["--version"], { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], timeout: 5000, }).trim(); - if (version.includes("Python 3")) return result; + if (version.includes("Python 3")) return first; } catch { // not found or not Python 3 } diff --git a/packages/cli/src/utils/clipboard.ts b/packages/cli/src/utils/clipboard.ts index fd4b962e6..f20dad725 100644 --- a/packages/cli/src/utils/clipboard.ts +++ b/packages/cli/src/utils/clipboard.ts @@ -31,9 +31,10 @@ function detectProvider(): ClipboardProvider | undefined { { cmd: "xclip", args: ["-selection", "clipboard"] }, { cmd: "xsel", args: ["--clipboard", "--input"] }, ]; + const cmd = process.platform === "win32" ? "where" : "which"; for (const p of candidates) { - const which = spawnSync("which", [p.cmd], { stdio: "ignore" }); - if (which.status === 0) return p; + const result = spawnSync(cmd, [p.cmd], { stdio: "ignore" }); + if (result.status === 0) return p; } return undefined; } diff --git a/packages/cli/src/whisper/manager.ts b/packages/cli/src/whisper/manager.ts index e2dffff60..4a1237a2a 100644 --- a/packages/cli/src/whisper/manager.ts +++ b/packages/cli/src/whisper/manager.ts @@ -22,12 +22,17 @@ function getModelUrl(model: string): string { function whichBinary(name: string): string | undefined { try { - const result = execFileSync("which", [name], { + const cmd = process.platform === "win32" ? "where" : "which"; + const output = execFileSync(cmd, [name], { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], timeout: 5000, - }).trim(); - return result || undefined; + }); + const first = output + .split(/\r?\n/) + .map((s) => s.trim()) + .find(Boolean); + return first || undefined; } catch { return undefined; }