import path from "node:path"; /** * Find the actual key used for PATH in the env object. * On Windows, `process.env` stores it as `Path` (not `PATH`), * and after copying to a plain object the original casing is preserved. */ export function findPathKey(env: Record): string { if ("PATH" in env) { return "PATH"; } for (const key of Object.keys(env)) { if (key.toUpperCase() === "PATH") { return key; } } return "PATH"; } export function normalizePathPrepend(entries?: string[]) { if (!Array.isArray(entries)) { return []; } const seen = new Set(); const normalized: string[] = []; for (const entry of entries) { if (typeof entry !== "string") { continue; } const trimmed = entry.trim(); if (!trimmed || seen.has(trimmed)) { continue; } seen.add(trimmed); normalized.push(trimmed); } return normalized; } export function mergePathPrepend(existing: string | undefined, prepend: string[]) { if (prepend.length === 0) { return existing; } const partsExisting = (existing ?? "") .split(path.delimiter) .map((part) => part.trim()) .filter(Boolean); const merged: string[] = []; const seen = new Set(); for (const part of [...prepend, ...partsExisting]) { if (seen.has(part)) { continue; } seen.add(part); merged.push(part); } return merged.join(path.delimiter); } export function applyPathPrepend( env: Record, prepend: string[] | undefined, options?: { requireExisting?: boolean }, ) { if (!Array.isArray(prepend) || prepend.length === 0) { return; } // On Windows the PATH key may be stored as `Path` (case-insensitive env vars). // After coercing to a plain object the original casing is preserved, so we must // look up the actual key to read the existing value and write the merged result back. const pathKey = findPathKey(env); if (options?.requireExisting && !env[pathKey]) { return; } const merged = mergePathPrepend(env[pathKey], prepend); if (merged) { env[pathKey] = merged; } }