UNPKG

@bearz/exec

Version:

The exec module makes it easy to spawn child_processes across different runtimes and different operating systems.

281 lines (280 loc) 9.21 kB
/** * The `path-finder` module provides a way to find the path of executables * across different platforms (Windows, Darwin, Linux). * * @module */ import { equalFold } from "@bearz/strings/equal"; import { expand, get } from "@bearz/env"; import { underscore } from "@bearz/strings/underscore"; import { which, whichSync } from "./which.js"; import { isFile, isFileSync } from "@bearz/fs"; import { DARWIN, WIN } from "./globals.js"; /** * Represents a path finder that allows storing and retrieving * options for finding an executable and methods to find the * executable. * * The path finder will use the options to look up by precendence: * * - If the full path to the executable is provided, it will be used. * - If an environment variable is provided, it will be used. * - If a cached path is provided, it will be used. * - If the executable is found in the system path, it will be used. * - If the executable is found in the windows paths when on Windows, it will be used. * - If the executable is found in the darwin paths when on Darwin or linux paths, it will be used. * - If the executable is found in the linux paths, it will be used. * * The paths for windows, darwin, and linux can contain environment variables e.g. * `${USERPROFILE}`, `${USER}`, or `%USERPROFILE% that will be expanded before checking if the file exists. * @example * ```ts * import { pathFinder } from "./path-finder.ts"; * * pathFinder.set("deno", { * name: "deno", * envVariable: "DENO_EXE", * windows: ["${USERPROFILE}\\.deno\\bin\\deno.exe"], * linux: ["${USER}/.deno/bin/deno"], * }); * * const deno = await pathFinder.findExe("deno"); * console.log(deno); * ``` */ export class PathFinder { #map; constructor() { this.#map = new Map(); } /** * Sets the path finder options for a given name. * @param name - The name of the path finder. * @param options - The path finder options. */ set(name, options) { this.#map.set(name, options); } /** * Retrieves the path finder options for a given name. * @param name - The name of the path finder. * @returns The path finder options, or undefined if not found. */ get(name) { return this.#map.get(name); } /** * Checks if a path finder with the given name exists. * @param name - The name of the path finder. * @returns True if the path finder exists, false otherwise. */ has(name) { return this.#map.has(name); } /** * Deletes the path finder with the given name. * @param name - The name of the path finder. * @returns True if the path finder was deleted, false otherwise. */ delete(name) { return this.#map.delete(name); } /** * Clears all path finders. */ clear() { this.#map.clear(); } /** * Finds the path finder options for a given name. * @param name - The name of the path finder. * @returns The path finder options, or undefined if not found. */ find(name) { const options = this.get(name); if (!options) { return; } for (const [key, value] of this.#map) { if (value.name === name) { return value; } if (value.cached === name) { return value; } if (equalFold(key, name)) { return value; } } return undefined; } /** * Finds the executable path for a given name. * @param name - The name of the executable. * @returns The executable path, or undefined if not found. */ async findExe(name) { let options = this.find(name); if (!options) { options = { name: name, envVariable: (underscore(name) + "_EXE").toUpperCase(), }; this.set(name, options); } if (options?.envVariable) { let envPath = get(options.envVariable); if (!options.noCache && envPath && envPath.length > 0 && options.cached === envPath) { return envPath; } envPath = expand(envPath ?? ""); if (!options.noCache && envPath && envPath.length > 0 && options.cached === envPath) { return envPath; } if (envPath && await isFile(envPath)) { options.cached = envPath; return envPath; } } if (!options.noCache && options.cached) { return options.cached; } const defaultPath = await which(name); if (defaultPath) { options.cached = defaultPath; return defaultPath; } if (WIN) { if (options.windows && options.windows.length) { for (const path of options.windows) { let next = path; next = expand(next); if (await isFile(next)) { options.cached = next; return next; } } } return undefined; } if (DARWIN) { if (options.darwin && options.darwin.length) { for (const path of options.darwin) { let next = path; next = expand(next); if (await isFile(next)) { options.cached = next; return next; } } } // allow darwin to use linux paths // do not return here } if (options.linux && options.linux.length) { for (const path of options.linux) { let next = path; next = expand(next); if (await isFile(next)) { options.cached = next; return next; } } } return undefined; } /** * Synchronously finds the executable path for a given name. * @param name - The name of the executable. * @returns The executable path, or undefined if not found. */ findExeSync(name) { let options = this.find(name); if (!options) { options = { name: name, envVariable: (underscore(name) + "_EXE").toUpperCase(), }; this.set(name, options); } if (options?.envVariable) { let envPath = get(options.envVariable); if (!options.noCache && envPath && envPath.length > 0 && options.cached === envPath) { return envPath; } envPath = expand(envPath ?? ""); if (!options.noCache && envPath && envPath.length > 0 && options.cached === envPath) { return envPath; } if (envPath) { if (envPath && isFileSync(envPath)) { options.cached = envPath; return envPath; } } } if (!options.noCache && options.cached) { return options.cached; } const defaultPath = whichSync(name); if (defaultPath) { options.cached = defaultPath; return defaultPath; } if (WIN) { if (options.windows && options.windows.length) { for (const path of options.windows) { let next = path; try { next = expand(next); } catch { continue; } if (isFileSync(next)) { options.cached = next; return next; } } } return undefined; } if (DARWIN) { if (options.darwin && options.darwin.length) { for (const path of options.darwin) { let next = path; try { next = expand(next); } catch { // todo: get trace/debug writer to handle continue; } if (isFileSync(next)) { options.cached = next; return next; } } } // allow darwin to use linux paths // do not return here } if (options.linux && options.linux.length) { for (const path of options.linux) { let next = path; try { next = expand(next); } catch { continue; } if (isFileSync(next)) { options.cached = next; return next; } } } return undefined; } } /** * The default global path finder instance. */ export const pathFinder = new PathFinder();