mirror of
https://github.com/garrytan/gstack.git
synced 2026-06-30 05:25:36 +02:00
fix(make-pdf): Bun.which-based binary resolution for browse + pdftotext on Windows
Extends v1.24.0.0's Bun.which + GSTACK_*_BIN override pattern (introduced in browse/src/claude-bin.ts via #1252) to the two other binary resolvers in the codebase: make-pdf/src/browseClient.ts:resolveBrowseBin and make-pdf/src/pdftotext.ts:resolvePdftotext. Same Windows quirks (fs.accessSync(X_OK) degrades to existence-check; `which` isn't available outside Git Bash; bun --compile --outfile X emits X.exe), same Bun.which-based fix shape, same env override convention. Changes: - GSTACK_BROWSE_BIN / GSTACK_PDFTOTEXT_BIN as the v1.24-aligned overrides; BROWSE_BIN / PDFTOTEXT_BIN remain as back-compat aliases. - Bun.which() replaces execFileSync('which', ...) for PATH lookup. Handles Windows PATHEXT natively; no more `where`-vs-`which` branch. - findExecutable(base) helper exported from each module, probes .exe/.cmd/.bat after the bare-path miss on win32. Linux/macOS behavior is bit-identical (isExecutable short-circuits before the win32 branch ever runs). - macCandidates renamed posixCandidates (always was — /opt/homebrew, /usr/local, /usr/bin). No Windows candidates added; Poppler installs scatter across Scoop/Chocolatey/portable zips and guessing causes false positives. - Error messages get a Windows install hint (scoop install poppler / oschwartz10612) and `setx` example for GSTACK_*_BIN. - Pre-existing test 'honors BROWSE_BIN when it points at a real executable' was hardcoded /bin/sh — made cross-platform via a REAL_EXE constant (cmd.exe on win32, /bin/sh on POSIX). Was a Windows-CI blocker on its own. Coordination: PR #1094 (@BkashJEE) covered browseClient.ts independently with a narrower scope; this PR's pdftotext + cross-platform tests + GSTACK_*_BIN naming are additive. Either order of merge works. Test plan: - bun test make-pdf/test/browseClient.test.ts make-pdf/test/pdftotext.test.ts on win32 — 29 pass, 0 fail (12 new assertions: findExecutable POSIX/win32/null, resolveBrowseBin GSTACK_BROWSE_BIN + BROWSE_BIN + precedence + quote-strip, same shape for resolvePdftotext + Windows install hint in error message). - POSIX branch unchanged — fs.accessSync(X_OK) on Linux/macOS short-circuits before any win32 logic runs, matching the v1.24 claude-bin.ts pattern.
This commit is contained in:
+56
-26
@@ -13,11 +13,14 @@
|
||||
* between paragraphs, and homoglyph substitution. We add a word-token
|
||||
* diff and a paragraph-boundary assertion on top.
|
||||
*
|
||||
* Resolution order for the pdftotext binary:
|
||||
* 1. $PDFTOTEXT_BIN env override
|
||||
* 2. `which pdftotext` on PATH
|
||||
* 3. standard Homebrew paths on macOS
|
||||
* 4. throws a friendly "install poppler" error
|
||||
* Resolution order for the pdftotext binary (v1.24-aligned):
|
||||
* 1. $GSTACK_PDFTOTEXT_BIN env override (preferred, matches v1.24 GSTACK_*_BIN pattern)
|
||||
* 2. $PDFTOTEXT_BIN env override (back-compat alias)
|
||||
* 3. PATH lookup via Bun.which('pdftotext') — handles Windows PATHEXT natively
|
||||
* 4. standard POSIX paths (Homebrew + distro) — no Windows candidates because
|
||||
* Poppler scatters across Scoop / Chocolatey / oschwartz10612-poppler-windows
|
||||
* and guessing causes false positives. Set GSTACK_PDFTOTEXT_BIN explicitly.
|
||||
* 5. throws a friendly "install poppler" error
|
||||
*
|
||||
* The wrapper is *optional at runtime*: production renders don't need it.
|
||||
* Only the CI gate and unit tests invoke pdftotext.
|
||||
@@ -41,30 +44,53 @@ export interface PdftotextInfo {
|
||||
flavor: "poppler" | "xpdf" | "unknown";
|
||||
}
|
||||
|
||||
/**
|
||||
* Probe a base path for executability, honoring Windows extension suffixes.
|
||||
* Matches browseClient.ts:findExecutable — duplicated rather than shared
|
||||
* because the two modules already duplicate isExecutable for compile-isolation.
|
||||
*/
|
||||
export function findExecutable(base: string): string | null {
|
||||
if (isExecutable(base)) return base;
|
||||
if (process.platform === "win32") {
|
||||
for (const ext of [".exe", ".cmd", ".bat"]) {
|
||||
const withExt = base + ext;
|
||||
if (isExecutable(withExt)) return withExt;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function resolveOverride(value: string | undefined, env: NodeJS.ProcessEnv): string | null {
|
||||
if (!value?.trim()) return null;
|
||||
const trimmed = value.trim().replace(/^"(.*)"$/, '$1');
|
||||
if (path.isAbsolute(trimmed)) return findExecutable(trimmed);
|
||||
const PATH = env.PATH ?? env.Path ?? '';
|
||||
return Bun.which(trimmed, { PATH }) ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Locate pdftotext. Throws PdftotextUnavailableError if none is found.
|
||||
*/
|
||||
export function resolvePdftotext(): PdftotextInfo {
|
||||
const envOverride = process.env.PDFTOTEXT_BIN;
|
||||
if (envOverride && isExecutable(envOverride)) {
|
||||
return describeBinary(envOverride);
|
||||
}
|
||||
export function resolvePdftotext(env: NodeJS.ProcessEnv = process.env): PdftotextInfo {
|
||||
// 1 + 2: env overrides (GSTACK_PDFTOTEXT_BIN preferred, PDFTOTEXT_BIN back-compat).
|
||||
const overrideRaw = env.GSTACK_PDFTOTEXT_BIN ?? env.PDFTOTEXT_BIN;
|
||||
const override = resolveOverride(overrideRaw, env);
|
||||
if (override) return describeBinary(override);
|
||||
|
||||
// Try PATH
|
||||
try {
|
||||
const which = execFileSync("which", ["pdftotext"], { encoding: "utf8" }).trim();
|
||||
if (which && isExecutable(which)) return describeBinary(which);
|
||||
} catch {
|
||||
// fall through
|
||||
}
|
||||
// 3: PATH lookup via Bun.which — handles Windows PATHEXT natively.
|
||||
const PATH = env.PATH ?? env.Path ?? '';
|
||||
const onPath = Bun.which('pdftotext', { PATH });
|
||||
if (onPath) return describeBinary(onPath);
|
||||
|
||||
// Common macOS Homebrew locations
|
||||
const macCandidates = [
|
||||
"/opt/homebrew/bin/pdftotext", // Apple Silicon
|
||||
// 4: POSIX-only standard locations. No Windows candidates — Poppler installs
|
||||
// scatter across Scoop/Chocolatey/portable zips and guessing causes false
|
||||
// positives. Windows users set GSTACK_PDFTOTEXT_BIN explicitly.
|
||||
const posixCandidates = [
|
||||
"/opt/homebrew/bin/pdftotext", // Apple Silicon Homebrew
|
||||
"/usr/local/bin/pdftotext", // Intel Mac or Linuxbrew
|
||||
"/usr/bin/pdftotext", // distro package
|
||||
];
|
||||
for (const candidate of macCandidates) {
|
||||
for (const candidate of posixCandidates) {
|
||||
if (isExecutable(candidate)) return describeBinary(candidate);
|
||||
}
|
||||
|
||||
@@ -75,12 +101,16 @@ export function resolvePdftotext(): PdftotextInfo {
|
||||
"(Runtime rendering does NOT need it. This only affects tests.)",
|
||||
"",
|
||||
"To install:",
|
||||
" macOS: brew install poppler",
|
||||
" Ubuntu: sudo apt-get install poppler-utils",
|
||||
" Fedora: sudo dnf install poppler-utils",
|
||||
" macOS: brew install poppler",
|
||||
" Ubuntu: sudo apt-get install poppler-utils",
|
||||
" Fedora: sudo dnf install poppler-utils",
|
||||
" Windows: scoop install poppler (or download from",
|
||||
" https://github.com/oschwartz10612/poppler-windows)",
|
||||
"",
|
||||
"Or set PDFTOTEXT_BIN to an explicit path:",
|
||||
" export PDFTOTEXT_BIN=/path/to/pdftotext",
|
||||
"Or set GSTACK_PDFTOTEXT_BIN to an explicit path:",
|
||||
process.platform === "win32"
|
||||
? ' setx GSTACK_PDFTOTEXT_BIN "C:\\path\\to\\pdftotext.exe"'
|
||||
: " export GSTACK_PDFTOTEXT_BIN=/path/to/pdftotext",
|
||||
].join("\n"));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user