Files
openclaw/src/infra/executable-path.ts
Josh Avant f4fef64fc1 Gateway: treat scope-limited probe RPC as degraded reachability (#45622)
* Gateway: treat scope-limited probe RPC as degraded

* Docs: clarify gateway probe degraded scope output

* test: fix CI type regressions in gateway and outbound suites

* Tests: fix Node24 diffs theme loading and Windows assertions

* Tests: fix extension typing after main rebase

* Tests: fix Windows CI regressions after rebase

* Tests: normalize executable path assertions on Windows

* Tests: remove duplicate gateway daemon result alias

* Tests: stabilize Windows approval path assertions

* Tests: fix Discord rate-limit startup fixture typing

* Tests: use Windows-friendly relative exec fixtures

---------

Co-authored-by: Mainframe <mainframe@MainfraacStudio.localdomain>
2026-03-13 23:13:33 -05:00

101 lines
2.7 KiB
TypeScript

import fs from "node:fs";
import path from "node:path";
import { expandHomePrefix } from "./home-dir.js";
function resolveWindowsExecutableExtensions(
executable: string,
env: NodeJS.ProcessEnv | undefined,
): string[] {
if (process.platform !== "win32") {
return [""];
}
if (path.extname(executable).length > 0) {
return [""];
}
return [
"",
...(
env?.PATHEXT ??
env?.Pathext ??
process.env.PATHEXT ??
process.env.Pathext ??
".EXE;.CMD;.BAT;.COM"
)
.split(";")
.map((ext) => ext.toLowerCase()),
];
}
function resolveWindowsExecutableExtSet(env: NodeJS.ProcessEnv | undefined): Set<string> {
return new Set(
(
env?.PATHEXT ??
env?.Pathext ??
process.env.PATHEXT ??
process.env.Pathext ??
".EXE;.CMD;.BAT;.COM"
)
.split(";")
.map((ext) => ext.toLowerCase())
.filter(Boolean),
);
}
export function isExecutableFile(filePath: string): boolean {
try {
const stat = fs.statSync(filePath);
if (!stat.isFile()) {
return false;
}
if (process.platform === "win32") {
const ext = path.extname(filePath).toLowerCase();
if (!ext) {
return true;
}
return resolveWindowsExecutableExtSet(undefined).has(ext);
}
fs.accessSync(filePath, fs.constants.X_OK);
return true;
} catch {
return false;
}
}
export function resolveExecutableFromPathEnv(
executable: string,
pathEnv: string,
env?: NodeJS.ProcessEnv,
): string | undefined {
const entries = pathEnv.split(path.delimiter).filter(Boolean);
const extensions = resolveWindowsExecutableExtensions(executable, env);
for (const entry of entries) {
for (const ext of extensions) {
const candidate = path.join(entry, executable + ext);
if (isExecutableFile(candidate)) {
return candidate;
}
}
}
return undefined;
}
export function resolveExecutablePath(
rawExecutable: string,
options?: { cwd?: string; env?: NodeJS.ProcessEnv },
): string | undefined {
const expanded = rawExecutable.startsWith("~")
? expandHomePrefix(rawExecutable, { env: options?.env })
: rawExecutable;
if (expanded.includes("/") || expanded.includes("\\")) {
if (path.isAbsolute(expanded)) {
return isExecutableFile(expanded) ? expanded : undefined;
}
const base = options?.cwd && options.cwd.trim() ? options.cwd.trim() : process.cwd();
const candidate = path.resolve(base, expanded);
return isExecutableFile(candidate) ? candidate : undefined;
}
const envPath =
options?.env?.PATH ?? options?.env?.Path ?? process.env.PATH ?? process.env.Path ?? "";
return resolveExecutableFromPathEnv(expanded, envPath, options?.env);
}