import type { Runtime } from "./runtime.ts"; const cache = new WeakMap>(); const WINDOWS_PATHEXT = [".COM", ".EXE", ".BAT", ".CMD"]; function isWindows(runtime: Runtime): boolean { return runtime.platform === "win32" || runtime.platform === "windows"; } function cacheFor(runtime: Runtime): Map { const existing = cache.get(runtime); if (existing) return existing; const created = new Map(); cache.set(runtime, created); return created; } /** * Resolve an executable without invoking a shell. The cache is scoped to the * injected runtime, and its key includes all inputs that affect resolution. */ export function which(name: string, runtime: Runtime): string | null { const trimmed = name.trim(); if (!trimmed || trimmed.includes("\0") || trimmed.includes("/") || trimmed.includes("\\")) return null; const pathValue = runtime.env.PATH ?? ""; const pathext = runtime.env.PATHEXT ?? ""; const key = `${runtime.platform}\0${pathValue}\0${pathext}\0${trimmed}`; const scopedCache = cacheFor(runtime); const cached = scopedCache.get(key); if (cached !== undefined) return cached; const windows = isWindows(runtime); const names = windows && !/\.[^./\\]+$/u.test(trimmed) ? [trimmed, ...((pathext ? pathext.split(";") : WINDOWS_PATHEXT).filter(Boolean).map(ext => `${trimmed}${ext}`))] : [trimmed]; for (const directory of pathValue.split(windows ? ";" : ":")) { const base = directory.trim(); if (!base) continue; for (const candidateName of names) { const separator = windows ? "\\" : "/"; const candidate = `${base.replace(/[\\/]+$/u, "")}${separator}${candidateName}`; try { if (runtime.isExecutable(candidate)) { scopedCache.set(key, candidate); return candidate; } } catch { // A broken PATH entry or inaccessible filesystem is simply not a hit. } } } scopedCache.set(key, null); return null; } export const $which = which;