From 68550f635b594d5067b899a0fa9fe8328b45edd4 Mon Sep 17 00:00:00 2001 From: Miguel Angel Simon Sierra Date: Thu, 29 Oct 2026 19:20:56 -0400 Subject: [PATCH] fix(cli): the ignored-binary note names only the missing binary, and covers .hyperframes/bin --- packages/cli/src/browser/ffmpeg.ts | 10 ++++++--- packages/parsers/src/ffBinaries.test.ts | 30 +++++++++++++++++++++++-- packages/parsers/src/ffBinaries.ts | 17 +++++++------- 3 files changed, 44 insertions(+), 13 deletions(-) diff --git a/packages/cli/src/browser/ffmpeg.ts b/packages/cli/src/browser/ffmpeg.ts index d2ad6384e4..a1f51f22ac 100644 --- a/packages/cli/src/browser/ffmpeg.ts +++ b/packages/cli/src/browser/ffmpeg.ts @@ -96,9 +96,13 @@ export function getFFmpegInstallCommand(): string | undefined { } export function getFFmpegInstallHint(): string { - const skipped = getSkippedX86Binary(); - const base = installHint(); - return skipped ? `${base} (ignored ${skipped}: x86_64 only, it cannot run on this Mac)` : base; + // Only a binary that is still missing: one that was found needs no explanation. + const ignored = (["ffmpeg", "ffprobe"] as const) + .filter((name) => !findFfBinary(name, { configuredMustExist: true })) + .map(getSkippedX86Binary) + .filter((path) => path !== undefined); + const note = ignored.map((path) => ` (ignored ${path}: x86_64 only, it cannot run on this Mac)`); + return installHint() + note.join(""); } function installHint(): string { diff --git a/packages/parsers/src/ffBinaries.test.ts b/packages/parsers/src/ffBinaries.test.ts index a9da5deae2..d28e72d73c 100644 --- a/packages/parsers/src/ffBinaries.test.ts +++ b/packages/parsers/src/ffBinaries.test.ts @@ -210,11 +210,14 @@ describe("findFfBinary", () => { return { project, binary }; } + let skipped: { ffmpeg?: string; ffprobe?: string } = {}; + async function findInProjectDir( project: string, name: "ffmpeg" | "ffprobe", systemBinaries: string[] = [], ) { + skipped = {}; vi.stubEnv("HYPERFRAMES_FFMPEG_PATH", ""); vi.stubEnv("HYPERFRAMES_FFPROBE_PATH", ""); process.env.PATH = ""; @@ -236,8 +239,10 @@ describe("findFfBinary", () => { return { ...mocked, default: mocked }; }); try { - const { findFfBinary } = await importFresh(); - return findFfBinary(name); + const { findFfBinary, getSkippedX86Binary } = await importFresh(); + const found = findFfBinary(name); + skipped = { ffmpeg: getSkippedX86Binary("ffmpeg"), ffprobe: getSkippedX86Binary("ffprobe") }; + return found; } finally { vi.unstubAllEnvs(); rmSync(project, { recursive: true, force: true }); @@ -294,6 +299,27 @@ describe("findFfBinary", () => { expect(await findInProjectDir(project, "ffprobe")).toBeUndefined(); }); + it("names the skipped binary under its own name only", async () => { + asAppleSilicon(); + const { project, binary } = installerProject("ffprobe", { content: machO(0x07) }); + + await findInProjectDir(project, "ffprobe"); + + expect(skipped).toEqual({ ffprobe: binary }); + }); + + it("skips an x86_64 binary in the project's .hyperframes/bin", async () => { + asAppleSilicon(); + const project = realpathSync(mkdtempSync(join(tmpdir(), "hf-ff-local-"))); + mkdirSync(join(project, ".hyperframes", "bin"), { recursive: true }); + const binary = join(project, ".hyperframes", "bin", "ffprobe"); + writeFileSync(binary, machO(0x07)); + chmodSync(binary, 0o755); + + expect(await findInProjectDir(project, "ffprobe")).toBeUndefined(); + expect(skipped.ffprobe).toBe(binary); + }); + it("keeps an arm64 installer binary", async () => { asAppleSilicon(); const { project, binary } = installerProject("ffprobe", { content: machO(0x0c) }); diff --git a/packages/parsers/src/ffBinaries.ts b/packages/parsers/src/ffBinaries.ts index 78e1322a6d..6e7b0d1aae 100644 --- a/packages/parsers/src/ffBinaries.ts +++ b/packages/parsers/src/ffBinaries.ts @@ -1,7 +1,7 @@ import { execFileSync } from "node:child_process"; import { accessSync, closeSync, constants, existsSync, openSync, readSync } from "node:fs"; import { createRequire } from "node:module"; -import { delimiter, dirname, join, resolve } from "node:path"; +import { basename, delimiter, dirname, join, resolve } from "node:path"; /** * Shared FFmpeg/FFprobe binary resolution for every package that shells out @@ -67,15 +67,16 @@ function runsOnThisMac(candidate: string): boolean { return true; } if (!isX86OnlyMachO(candidate)) return true; - skippedX86Binary ??= candidate; + const name = basename(candidate) as FfBinaryName; + if (!skippedX86Binaries.has(name)) skippedX86Binaries.set(name, candidate); return false; } -let skippedX86Binary: string | undefined; +const skippedX86Binaries = new Map(); -/** The first x86_64-only binary skipped on this Mac, for naming in the "not found" hint. */ -export function getSkippedX86Binary(): string | undefined { - return skippedX86Binary; +/** The first x86_64-only `name` binary skipped on this Mac, for naming in the "not found" hint. */ +export function getSkippedX86Binary(name: FfBinaryName): string | undefined { + return skippedX86Binaries.get(name); } function isExecutablePathCandidate(candidate: string): boolean { @@ -143,7 +144,7 @@ function binaryFileName(name: FfBinaryName): string { function findInProjectLocalBin(name: FfBinaryName): string | undefined { const candidate = resolve(".hyperframes", "bin", binaryFileName(name)); - return existsSync(candidate) ? candidate : undefined; + return existsSync(candidate) && runsOnThisMac(candidate) ? candidate : undefined; } function tryResolve(fromFile: string, request: string): string | undefined { @@ -242,5 +243,5 @@ export function findFfBinary( export function clearFfBinaryLookupCache(): void { pathLookupCache.clear(); lastMissAt.clear(); - skippedX86Binary = undefined; + skippedX86Binaries.clear(); }