import { execFile } from "node:child_process"; import { constants } from "node:fs"; import { access, realpath } from "node:fs/promises"; import { delimiter, join } from "node:path"; const TOOL_NAMES = ["node", "npm", "npx"] as const; const MAX_OUTPUT_BYTES = 65_536; const MAX_DIAGNOSTIC_BYTES = 4_096; const PROBE_CONCURRENCY = 4; type RuntimeToolName = (typeof TOOL_NAMES)[number]; type RuntimeProbeFailureCode = | "runtime_dynamic_library_missing" | "runtime_invalid_version_output" | "runtime_timeout" | "runtime_signal" | "runtime_spawn_failed" | "runtime_nonzero_exit"; /** Stable, bounded classification of one failed executable probe. */ interface RuntimeProbeFailure { readonly code: RuntimeProbeFailureCode; readonly exit_code: number | null; readonly signal: NodeJS.Signals | null; readonly dependency: string | null; readonly stderr: string; } interface RuntimeExecutableIdentity { readonly tool: RuntimeToolName; readonly lexical_path: string; readonly canonical_path: string; readonly path_index: number | null; readonly selection: "rea-launcher" | "path-primary" | "path-shadowed"; } /** One lexical PATH candidate and its canonical executable probe result. */ type RuntimeExecutableDiagnostic = RuntimeExecutableIdentity & ( | { readonly version: string; readonly healthy: true; readonly failure: null; } | { readonly version: null; readonly healthy: false; readonly failure: RuntimeProbeFailure; } ); /** Runtime identities observed under one exact effective environment. */ export interface RuntimeExecutableInventory { readonly launcher_node: string; readonly candidates: readonly RuntimeExecutableDiagnostic[]; } /** Host inputs that determine executable discovery and shebang resolution. */ export interface RuntimeExecutableInventoryOptions { readonly platform: NodeJS.Platform; readonly path: string; readonly launcherNode: string; readonly pathExtensions?: readonly string[]; readonly timeoutMs?: number; } /** Inventory and bounded-probe Node toolchain candidates under one exact PATH. */ export const inspectRuntimeExecutables = async ( options: RuntimeExecutableInventoryOptions, ): Promise => { const pathEntries = unique(options.path.split(delimiter).filter(Boolean)); const candidates: Array<{ readonly tool: RuntimeToolName; readonly path: string; readonly pathIndex: number | null; readonly selection: RuntimeExecutableDiagnostic["selection"]; }> = [ { tool: "node", path: options.launcherNode, pathIndex: null, selection: "rea-launcher", }, ]; for (const tool of TOOL_NAMES) { const discovered = await discoverToolCandidates( tool, pathEntries, options.platform, options.pathExtensions, ); for (const [index, candidate] of discovered.entries()) candidates.push({ tool, path: candidate.path, pathIndex: candidate.pathIndex, selection: index === 0 ? "path-primary" : "path-shadowed", }); } const seen = new Set(); const uniqueCandidates: typeof candidates = []; for (const candidate of candidates) { const key = `${candidate.tool}\0${candidate.path}`; if (seen.has(key)) continue; seen.add(key); uniqueCandidates.push(candidate); } const diagnostics = await mapConcurrentBounded( uniqueCandidates, PROBE_CONCURRENCY, (candidate) => probeCandidate(candidate, options.timeoutMs ?? 5_000, options.path), ); return { launcher_node: await canonicalPath(options.launcherNode), candidates: diagnostics, }; }; const discoverToolCandidates = async ( tool: RuntimeToolName, pathEntries: readonly string[], platform: NodeJS.Platform, configuredExtensions: readonly string[] | undefined, ): Promise< readonly { readonly path: string; readonly pathIndex: number }[] > => { const extensions = platform === "win32" ? (configuredExtensions ?? [".COM", ".EXE", ".BAT", ".CMD"]) : [""]; const candidates: Array<{ readonly path: string; readonly pathIndex: number; }> = []; for (const [pathIndex, directory] of pathEntries.entries()) { for (const extension of extensions) { const path = join(directory, `${tool}${extension}`); if (await isExecutable(path, platform)) { candidates.push({ path, pathIndex }); break; } } } return candidates; }; const probeCandidate = async ( candidate: { readonly tool: RuntimeToolName; readonly path: string; readonly pathIndex: number | null; readonly selection: RuntimeExecutableDiagnostic["selection"]; }, timeoutMs: number, effectivePath: string, ): Promise => { const canonical = await canonicalPath(candidate.path); const result = await executeVersion(candidate.path, timeoutMs, effectivePath); const identity = { tool: candidate.tool, lexical_path: candidate.path, canonical_path: canonical, path_index: candidate.pathIndex, selection: candidate.selection, }; return result.ok ? { ...identity, version: result.version, healthy: true, failure: null } : { ...identity, version: null, healthy: false, failure: result.failure, }; }; type VersionResult = | { readonly ok: true; readonly version: string } | { readonly ok: false; readonly failure: RuntimeProbeFailure }; const executeVersion = ( path: string, timeoutMs: number, effectivePath: string, ): Promise => new Promise((resolve) => { const child = execFile( path, ["--version"], { encoding: "utf8", timeout: timeoutMs, maxBuffer: MAX_OUTPUT_BYTES, windowsHide: true, env: environmentWithPath(effectivePath), }, (cause: Error | null, stdout: string, stderr: string) => { if (cause === null) { const version = firstNonemptyLine(stdout) ?? firstNonemptyLine(stderr); if (version !== undefined) resolve({ ok: true, version }); else resolve({ ok: false, failure: failure("runtime_invalid_version_output", child, stderr), }); return; } resolve({ ok: false, failure: classifyProbeFailure(cause, child, stderr), }); }, ); }); const classifyProbeFailure = ( cause: Error, child: ReturnType, stderr: string, ): RuntimeProbeFailure => { const dependency = missingDynamicLibrary(stderr); if (dependency !== null) return failure( "runtime_dynamic_library_missing", child, stderr, dependency, ); if ("killed" in cause && cause.killed === true) return failure("runtime_timeout", child, stderr); if (child.signalCode !== null) return failure("runtime_signal", child, stderr); if ("code" in cause && typeof cause.code === "string") return failure("runtime_spawn_failed", child, stderr); return failure("runtime_nonzero_exit", child, stderr); }; const failure = ( code: RuntimeProbeFailureCode, child: ReturnType, stderr: string, dependency: string | null = null, ): RuntimeProbeFailure => ({ code, exit_code: child.exitCode, signal: child.signalCode, dependency, stderr: bounded(stderr.trim()), }); const missingDynamicLibrary = (stderr: string): string | null => /(?:dyld(?:\[[^\]]+\])?: )?Library not loaded:\s*([^\r\n]+)/u .exec(stderr)?.[1] ?.trim() ?? null; const isExecutable = async ( path: string, platform: NodeJS.Platform, ): Promise => { try { await access(path, platform === "win32" ? constants.F_OK : constants.X_OK); return true; } catch { return false; } }; const canonicalPath = async (path: string): Promise => { try { return await realpath(path); } catch { return path; } }; const firstNonemptyLine = (value: string): string | undefined => value .split(/\r?\n/u) .find((line) => line.trim().length > 0) ?.trim(); const bounded = (value: string): string => value.length <= MAX_DIAGNOSTIC_BYTES ? value : value.slice(-MAX_DIAGNOSTIC_BYTES); const unique = (values: readonly string[]): string[] => [...new Set(values)]; const environmentWithPath = (path: string): NodeJS.ProcessEnv => { const environment: NodeJS.ProcessEnv = {}; for (const [name, value] of Object.entries(process.env)) if (name.toUpperCase() !== "PATH") environment[name] = value; environment.PATH = path; return environment; }; const mapConcurrentBounded = async ( inputs: readonly Input[], concurrency: number, operation: (input: Input) => Promise, ): Promise => { const outputs: Output[] = []; let nextIndex = 0; const workers = Array.from( { length: Math.min(concurrency, inputs.length) }, async () => { while (nextIndex < inputs.length) { const index = nextIndex; nextIndex += 1; const input = inputs[index]; if (input !== undefined) outputs[index] = await operation(input); } }, ); await Promise.all(workers); return outputs; };