import { accessSync, constants, statSync } from "node:fs"; import { delimiter, resolve } from "node:path"; export interface ExecutableLookupOptions { path?: string; platform?: string; } function pathValue( platform: string, path: string | undefined, ): string | undefined { if (path !== undefined) return path; if (platform !== "win32") return process.env.PATH; return Object.entries(process.env).find( ([key]) => key.toLowerCase() === "path", )?.[1]; } export function findExecutableFile( path: string, options: Pick = {}, ): string | undefined { try { if (!statSync(path).isFile()) return undefined; if ((options.platform ?? process.platform) !== "win32") accessSync(path, constants.X_OK); return path; } catch { return undefined; } } export function findExecutable( names: readonly string[], options: ExecutableLookupOptions = {}, ): string | undefined { const platform = options.platform ?? process.platform; const windows = platform === "win32"; const path = pathValue(platform, options.path); if (!path) return undefined; for (const rawDirectory of path.split(windows ? ";" : delimiter)) { const directory = windows && rawDirectory.startsWith('"') && rawDirectory.endsWith('"') ? rawDirectory.slice(1, -1) : rawDirectory; if (!directory) continue; for (const name of names) { if (name.includes("/") || name.includes("\\")) continue; const candidate = resolve(directory, name); if (findExecutableFile(candidate, { platform })) return candidate; } } return undefined; }