mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-28 13:26:04 -06:00
fix: include pnpm 11 bins in gateway PATH (#85238)
* fix: include pnpm 11 bins in gateway PATH * fix: include pnpm 11 bins in gateway PATH * fix(security): reject workspace package-manager PATH roots * fix(infra): preserve package paths from root cwd --------- Co-authored-by: openclaw-clownfish[bot] <280122609+openclaw-clownfish[bot]@users.noreply.github.com> Co-authored-by: Vincent Koc <25068+vincentkoc@users.noreply.github.com> Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
This commit is contained in:
@@ -36,6 +36,7 @@ describe("getMinimalServicePathParts - Linux user directories", () => {
|
||||
expect(result).toContain("/home/testuser/.fnm/current/bin");
|
||||
expect(result).toContain("/home/testuser/.volta/bin");
|
||||
expect(result).toContain("/home/testuser/.asdf/shims");
|
||||
expect(result).toContain("/home/testuser/.local/share/pnpm/bin");
|
||||
expect(result).toContain("/home/testuser/.local/share/pnpm");
|
||||
expect(result).toContain("/home/testuser/.bun/bin");
|
||||
});
|
||||
@@ -50,7 +51,7 @@ describe("getMinimalServicePathParts - Linux user directories", () => {
|
||||
expect(result).toEqual(["/usr/local/bin", "/usr/bin", "/bin"]);
|
||||
});
|
||||
|
||||
it("places user directories before system directories on Linux", () => {
|
||||
it("places user directories after system directories on Linux", () => {
|
||||
const result = getMinimalServicePathParts({
|
||||
platform: "linux",
|
||||
home: "/home/testuser",
|
||||
@@ -62,7 +63,33 @@ describe("getMinimalServicePathParts - Linux user directories", () => {
|
||||
|
||||
expect(userDirIndex).toBeGreaterThan(-1);
|
||||
expect(systemDirIndex).toBeGreaterThan(-1);
|
||||
expect(userDirIndex).toBeLessThan(systemDirIndex);
|
||||
expect(systemDirIndex).toBeLessThan(userDirIndex);
|
||||
});
|
||||
|
||||
it("places package-manager bin directories after trusted system directories on Linux", () => {
|
||||
const result = getMinimalServicePathPartsFromEnv({
|
||||
platform: "linux",
|
||||
env: {
|
||||
HOME: "/home/testuser",
|
||||
PNPM_HOME: "/home/testuser/.local/share/pnpm",
|
||||
NPM_CONFIG_PREFIX: "/home/testuser/.npm-global",
|
||||
},
|
||||
existsSync: allExist,
|
||||
});
|
||||
|
||||
const systemDirIndex = result.indexOf("/usr/bin");
|
||||
const packageManagerDirs = [
|
||||
"/home/testuser/.local/share/pnpm",
|
||||
"/home/testuser/.local/share/pnpm/bin",
|
||||
"/home/testuser/.npm-global/bin",
|
||||
];
|
||||
|
||||
expect(systemDirIndex).toBeGreaterThan(-1);
|
||||
for (const dir of packageManagerDirs) {
|
||||
const dirIndex = result.indexOf(dir);
|
||||
expect(dirIndex).toBeGreaterThan(-1);
|
||||
expect(systemDirIndex).toBeLessThan(dirIndex);
|
||||
}
|
||||
});
|
||||
|
||||
it("places extraDirs before user directories on Linux", () => {
|
||||
@@ -98,6 +125,7 @@ describe("getMinimalServicePathParts - Linux user directories", () => {
|
||||
});
|
||||
|
||||
expect(result).toContain("/opt/pnpm");
|
||||
expect(result).toContain("/opt/pnpm/bin");
|
||||
expect(result).toContain("/opt/npm/bin");
|
||||
expect(result).toContain("/opt/bun/bin");
|
||||
expect(result).toContain("/opt/volta/bin");
|
||||
@@ -222,7 +250,9 @@ describe("getMinimalServicePathParts - Linux user directories", () => {
|
||||
"/Users/testuser/Library/Application Support/fnm/aliases/default/bin",
|
||||
);
|
||||
expect(result).not.toContain("/Users/testuser/.fnm/aliases/default/bin");
|
||||
expect(result).not.toContain("/Users/testuser/Library/pnpm/bin");
|
||||
expect(result).not.toContain("/Users/testuser/Library/pnpm");
|
||||
expect(result).not.toContain("/Users/testuser/.local/share/pnpm/bin");
|
||||
expect(result).not.toContain("/Users/testuser/.local/share/pnpm");
|
||||
});
|
||||
|
||||
@@ -256,6 +286,7 @@ describe("getMinimalServicePathParts - Linux user directories", () => {
|
||||
});
|
||||
|
||||
expect(result).toContain("/opt/pnpm");
|
||||
expect(result).toContain("/opt/pnpm/bin");
|
||||
expect(result).toContain("/opt/volta/bin");
|
||||
expect(result).toContain("/opt/bun/bin");
|
||||
expect(result).toContain("/opt/asdf/shims");
|
||||
@@ -333,6 +364,7 @@ describe("getMinimalServicePathParts - Linux user directories", () => {
|
||||
});
|
||||
|
||||
expect(result).toContain("/home/testuser/.local/share/pnpm");
|
||||
expect(result).toContain("/home/testuser/.local/share/pnpm/bin");
|
||||
expect(result).toContain("/home/testuser/.local/share/fnm/aliases/default/bin");
|
||||
expect(result).toContain("/home/testuser/.local/share/fnm/current/bin");
|
||||
});
|
||||
@@ -515,6 +547,7 @@ describe("buildMinimalServicePath", () => {
|
||||
// Verify user directories are included
|
||||
expect(parts).toContain("/home/alice/.local/bin");
|
||||
expect(parts).toContain("/home/alice/.npm-global/bin");
|
||||
expect(parts).toContain("/home/alice/.local/share/pnpm/bin");
|
||||
expect(parts).toContain("/home/alice/.nvm/current/bin");
|
||||
expect(parts).toContain("/home/alice/.local/share/fnm/aliases/default/bin");
|
||||
|
||||
@@ -535,7 +568,7 @@ describe("buildMinimalServicePath", () => {
|
||||
expect(parts).toEqual(["/usr/local/bin", "/usr/bin", "/bin"]);
|
||||
});
|
||||
|
||||
it("ensures user directories come before system directories on Linux", () => {
|
||||
it("ensures user directories come after system directories on Linux", () => {
|
||||
const result = buildMinimalServicePath({
|
||||
platform: "linux",
|
||||
env: { HOME: "/home/bob" },
|
||||
@@ -546,7 +579,7 @@ describe("buildMinimalServicePath", () => {
|
||||
const firstUserDirIdx = parts.indexOf("/home/bob/.local/bin");
|
||||
const firstSystemDirIdx = parts.indexOf("/usr/local/bin");
|
||||
|
||||
expect(firstUserDirIdx).toBeLessThan(firstSystemDirIdx);
|
||||
expect(firstSystemDirIdx).toBeLessThan(firstUserDirIdx);
|
||||
});
|
||||
|
||||
it("includes extra directories when provided", () => {
|
||||
|
||||
@@ -194,6 +194,7 @@ function addCommonEnvConfiguredBinDirs(
|
||||
options: Pick<MinimalServicePathOptions, "cwd" | "home">,
|
||||
): void {
|
||||
addEnvConfiguredBinDir(dirs, env?.PNPM_HOME, options);
|
||||
addEnvConfiguredBinDir(dirs, appendSubdir(env?.PNPM_HOME, "bin"), options);
|
||||
addEnvConfiguredBinDir(dirs, appendSubdir(env?.NPM_CONFIG_PREFIX, "bin"), options);
|
||||
addEnvConfiguredBinDir(dirs, appendSubdir(env?.BUN_INSTALL, "bin"), options);
|
||||
addEnvConfiguredBinDir(dirs, appendSubdir(env?.VOLTA_HOME, "bin"), options);
|
||||
@@ -274,7 +275,7 @@ function resolveDarwinUserBinDirs(
|
||||
addEnvConfiguredBinDir(dirs, env?.NVM_DIR, pathOptions);
|
||||
// fnm: use aliases/default (not current)
|
||||
addEnvConfiguredBinDir(dirs, appendSubdir(env?.FNM_DIR, "aliases/default/bin"), pathOptions);
|
||||
// pnpm: binary is directly in PNPM_HOME (not in bin subdirectory)
|
||||
// pnpm 10 placed binaries directly in PNPM_HOME; pnpm 11 uses PNPM_HOME/bin.
|
||||
|
||||
// Common user bin directories
|
||||
addCommonUserBinDirs(dirs, home, existsSync, includeMissingUserBinDefaults);
|
||||
@@ -288,7 +289,9 @@ function resolveDarwinUserBinDirs(
|
||||
addExistingDir(dirs, `${home}/Library/Application Support/fnm/aliases/default/bin`, existsSync); // fnm default
|
||||
addExistingDir(dirs, `${home}/.fnm/aliases/default/bin`, existsSync); // fnm if customized to ~/.fnm
|
||||
// pnpm: macOS default is ~/Library/pnpm, not ~/.local/share/pnpm
|
||||
addExistingDir(dirs, `${home}/Library/pnpm/bin`, existsSync); // pnpm 11 default
|
||||
addExistingDir(dirs, `${home}/Library/pnpm`, existsSync); // pnpm default
|
||||
addExistingDir(dirs, `${home}/.local/share/pnpm/bin`, existsSync); // pnpm 11 XDG fallback
|
||||
addExistingDir(dirs, `${home}/.local/share/pnpm`, existsSync); // pnpm XDG fallback
|
||||
|
||||
return dirs;
|
||||
@@ -330,6 +333,7 @@ function resolveLinuxUserBinDirs(
|
||||
addExistingDir(dirs, `${home}/.local/share/fnm/current/bin`, existsSync); // fnm legacy current symlink
|
||||
addExistingDir(dirs, `${home}/.fnm/aliases/default/bin`, existsSync); // fnm if customized to ~/.fnm
|
||||
addExistingDir(dirs, `${home}/.fnm/current/bin`, existsSync); // fnm legacy current symlink
|
||||
addExistingDir(dirs, `${home}/.local/share/pnpm/bin`, existsSync); // pnpm 11 global bin
|
||||
addExistingDir(dirs, `${home}/.local/share/pnpm`, existsSync); // pnpm global bin
|
||||
|
||||
return dirs;
|
||||
@@ -369,10 +373,10 @@ export function getMinimalServicePathParts(options: MinimalServicePathOptions =
|
||||
for (const dir of extraDirs) {
|
||||
add(dir);
|
||||
}
|
||||
for (const dir of userDirs) {
|
||||
for (const dir of systemDirs) {
|
||||
add(dir);
|
||||
}
|
||||
for (const dir of systemDirs) {
|
||||
for (const dir of userDirs) {
|
||||
add(dir);
|
||||
}
|
||||
|
||||
|
||||
@@ -456,18 +456,22 @@ describe("loadDotEnv", () => {
|
||||
await withDotEnvFixture(async ({ base, cwdDir }) => {
|
||||
const bundledPluginsDir = path.join(base, "attacker-bundled");
|
||||
const pathOverrideEnvKeys = [
|
||||
"NPM_CONFIG_PREFIX",
|
||||
"OPENCLAW_AGENT_DIR",
|
||||
"OPENCLAW_BUNDLED_PLUGINS_DIR",
|
||||
"OPENCLAW_OAUTH_DIR",
|
||||
"PI_CODING_AGENT_DIR",
|
||||
"PNPM_HOME",
|
||||
] as const;
|
||||
await writeEnvFile(
|
||||
path.join(cwdDir, ".env"),
|
||||
[
|
||||
`NPM_CONFIG_PREFIX=${path.join(cwdDir, ".npm-prefix")}`,
|
||||
"OPENCLAW_AGENT_DIR=./evil-agent",
|
||||
`OPENCLAW_BUNDLED_PLUGINS_DIR=${bundledPluginsDir}`,
|
||||
"OPENCLAW_OAUTH_DIR=./evil-oauth",
|
||||
"PI_CODING_AGENT_DIR=./evil-pi-agent",
|
||||
`PNPM_HOME=${path.join(cwdDir, ".pnpm")}`,
|
||||
].join("\n"),
|
||||
);
|
||||
|
||||
|
||||
@@ -117,7 +117,9 @@ const BLOCKED_WORKSPACE_DOTENV_KEYS = new Set([
|
||||
"MINIMAX_API_HOST",
|
||||
"NODE_TLS_REJECT_UNAUTHORIZED",
|
||||
"NO_PROXY",
|
||||
"NPM_CONFIG_PREFIX",
|
||||
"NPM_EXECPATH",
|
||||
"PNPM_HOME",
|
||||
"OPENAI_API_KEYS",
|
||||
"OPENCLAW_AGENT_DIR",
|
||||
"OPENCLAW_ALLOW_PLUGIN_INSTALL_OVERRIDES",
|
||||
|
||||
+150
-1
@@ -1,4 +1,5 @@
|
||||
// Covers OpenClaw CLI PATH construction.
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { ensureOpenClawCliOnPath } from "./path-env.js";
|
||||
@@ -51,6 +52,8 @@ describe("ensureOpenClawCliOnPath", () => {
|
||||
"OPENCLAW_PATH_BOOTSTRAPPED",
|
||||
"OPENCLAW_ALLOW_PROJECT_LOCAL_BIN",
|
||||
"MISE_DATA_DIR",
|
||||
"PNPM_HOME",
|
||||
"NPM_CONFIG_PREFIX",
|
||||
"HOMEBREW_PREFIX",
|
||||
"HOMEBREW_BREW_FILE",
|
||||
"XDG_BIN_HOME",
|
||||
@@ -106,6 +109,8 @@ describe("ensureOpenClawCliOnPath", () => {
|
||||
delete process.env.HOMEBREW_PREFIX;
|
||||
delete process.env.HOMEBREW_BREW_FILE;
|
||||
delete process.env.XDG_BIN_HOME;
|
||||
delete process.env.PNPM_HOME;
|
||||
delete process.env.NPM_CONFIG_PREFIX;
|
||||
}
|
||||
|
||||
function expectPathsAfter(parts: string[], anchor: string, expectedPaths: string[]) {
|
||||
@@ -292,12 +297,17 @@ describe("ensureOpenClawCliOnPath", () => {
|
||||
it("places all user-writable home dirs after system dirs", () => {
|
||||
const { tmp, appCli } = setupAppCliRoot("case-user-writable-after-system");
|
||||
const localBin = path.join(tmp, ".local", "bin");
|
||||
const npmGlobalBin = path.join(tmp, ".npm-global", "bin");
|
||||
const pnpm11Bin = path.join(tmp, ".local", "share", "pnpm", "bin");
|
||||
const pnpmBin = path.join(tmp, ".local", "share", "pnpm");
|
||||
const bunBin = path.join(tmp, ".bun", "bin");
|
||||
const yarnBin = path.join(tmp, ".yarn", "bin");
|
||||
setDir(path.join(tmp, ".local"));
|
||||
setDir(localBin);
|
||||
setDir(path.join(tmp, ".npm-global"));
|
||||
setDir(npmGlobalBin);
|
||||
setDir(path.join(tmp, ".local", "share"));
|
||||
setDir(pnpm11Bin);
|
||||
setDir(pnpmBin);
|
||||
setDir(path.join(tmp, ".bun"));
|
||||
setDir(bunBin);
|
||||
@@ -312,7 +322,146 @@ describe("ensureOpenClawCliOnPath", () => {
|
||||
homeDir: tmp,
|
||||
platform: "linux",
|
||||
});
|
||||
expectPathsAfter(updated, "/usr/bin", [localBin, pnpmBin, bunBin, yarnBin]);
|
||||
expectPathsAfter(updated, "/usr/bin", [
|
||||
localBin,
|
||||
npmGlobalBin,
|
||||
pnpm11Bin,
|
||||
pnpmBin,
|
||||
bunBin,
|
||||
yarnBin,
|
||||
]);
|
||||
});
|
||||
|
||||
it("appends package-manager env bin dirs after system dirs", () => {
|
||||
const { tmp, appCli } = setupAppCliRoot("case-package-manager-env");
|
||||
const pnpmHome = path.join(tmp, "pnpm-home");
|
||||
const pnpmHomeBin = path.join(pnpmHome, "bin");
|
||||
const npmPrefix = path.join(tmp, "npm-prefix");
|
||||
const npmPrefixBin = path.join(npmPrefix, "bin");
|
||||
setDir(pnpmHome);
|
||||
setDir(pnpmHomeBin);
|
||||
setDir(npmPrefix);
|
||||
setDir(npmPrefixBin);
|
||||
|
||||
resetBootstrapEnv("/usr/bin:/bin");
|
||||
process.env.PNPM_HOME = pnpmHome;
|
||||
process.env.NPM_CONFIG_PREFIX = npmPrefix;
|
||||
|
||||
const updated = bootstrapPath({
|
||||
execPath: appCli,
|
||||
cwd: tmp,
|
||||
homeDir: tmp,
|
||||
platform: "linux",
|
||||
});
|
||||
expectPathsAfter(updated, "/usr/bin", [pnpmHome, pnpmHomeBin, npmPrefixBin]);
|
||||
});
|
||||
|
||||
it("keeps package-manager env roots when cwd is the filesystem root", () => {
|
||||
const { tmp, appCli } = setupAppCliRoot("case-package-manager-root-cwd");
|
||||
const pnpmHome = path.join(tmp, "pnpm-home");
|
||||
const pnpmHomeBin = path.join(pnpmHome, "bin");
|
||||
const npmPrefix = path.join(tmp, "npm-prefix");
|
||||
const npmPrefixBin = path.join(npmPrefix, "bin");
|
||||
for (const dir of [pnpmHome, pnpmHomeBin, npmPrefix, npmPrefixBin]) {
|
||||
setDir(dir);
|
||||
}
|
||||
|
||||
resetBootstrapEnv("/usr/bin:/bin");
|
||||
process.env.PNPM_HOME = pnpmHome;
|
||||
process.env.NPM_CONFIG_PREFIX = npmPrefix;
|
||||
|
||||
const updated = bootstrapPath({
|
||||
execPath: appCli,
|
||||
cwd: path.parse(tmp).root,
|
||||
homeDir: tmp,
|
||||
platform: "linux",
|
||||
});
|
||||
|
||||
expectPathsAfter(updated, "/usr/bin", [pnpmHome, pnpmHomeBin, npmPrefixBin]);
|
||||
});
|
||||
|
||||
it("ignores relative package-manager env roots", () => {
|
||||
const { tmp, appCli } = setupAppCliRoot("case-package-manager-relative");
|
||||
resetBootstrapEnv("/usr/bin:/bin");
|
||||
process.env.PNPM_HOME = ".";
|
||||
process.env.NPM_CONFIG_PREFIX = "npm-prefix";
|
||||
|
||||
const updated = bootstrapPath({
|
||||
execPath: appCli,
|
||||
cwd: tmp,
|
||||
homeDir: tmp,
|
||||
platform: "linux",
|
||||
});
|
||||
|
||||
expect(updated).not.toContain(".");
|
||||
expect(updated).not.toContain("bin");
|
||||
expect(updated).not.toContain(path.join("npm-prefix", "bin"));
|
||||
});
|
||||
|
||||
it("ignores package-manager env roots derived from the active workspace", () => {
|
||||
const homeDir = abs("/tmp/openclaw-path/home");
|
||||
const cwd = path.join(homeDir, "workspace");
|
||||
const appBinDir = path.join(homeDir, "app-bin");
|
||||
const appCli = path.join(appBinDir, "openclaw");
|
||||
const pnpmHome = path.join(cwd, ".pnpm");
|
||||
const npmPrefix = path.join(cwd, ".npm-prefix");
|
||||
for (const dir of [homeDir, cwd, appBinDir, pnpmHome, path.join(pnpmHome, "bin"), npmPrefix]) {
|
||||
setDir(dir);
|
||||
}
|
||||
setDir(path.join(npmPrefix, "bin"));
|
||||
setExe(appCli);
|
||||
resetBootstrapEnv("/usr/bin:/bin");
|
||||
process.env.PNPM_HOME = pnpmHome;
|
||||
process.env.NPM_CONFIG_PREFIX = npmPrefix;
|
||||
|
||||
const updated = bootstrapPath({
|
||||
execPath: appCli,
|
||||
cwd,
|
||||
homeDir,
|
||||
platform: "linux",
|
||||
});
|
||||
|
||||
expect(updated).not.toContain(pnpmHome);
|
||||
expect(updated).not.toContain(path.join(pnpmHome, "bin"));
|
||||
expect(updated).not.toContain(path.join(npmPrefix, "bin"));
|
||||
});
|
||||
|
||||
it("ignores package-manager env roots whose existing parent resolves into the workspace", () => {
|
||||
const homeDir = abs("/tmp/openclaw-path/home");
|
||||
const cwd = path.join(homeDir, "workspace");
|
||||
const appBinDir = path.join(homeDir, "app-bin");
|
||||
const appCli = path.join(appBinDir, "openclaw");
|
||||
for (const dir of [homeDir, cwd, appBinDir]) {
|
||||
setDir(dir);
|
||||
}
|
||||
setExe(appCli);
|
||||
resetBootstrapEnv("/usr/bin:/bin");
|
||||
process.env.PNPM_HOME = "/tmp/workspace-link/missing-pnpm-home";
|
||||
|
||||
const realpathNative = vi.spyOn(fs.realpathSync, "native").mockImplementation((candidate) => {
|
||||
const value = String(candidate);
|
||||
if (value === "/tmp/workspace-link") {
|
||||
return cwd;
|
||||
}
|
||||
if (value === cwd || value === homeDir) {
|
||||
return value;
|
||||
}
|
||||
throw Object.assign(new Error("missing"), { code: "ENOENT" });
|
||||
});
|
||||
|
||||
try {
|
||||
const updated = bootstrapPath({
|
||||
execPath: appCli,
|
||||
cwd,
|
||||
homeDir,
|
||||
platform: "linux",
|
||||
});
|
||||
|
||||
expect(updated).not.toContain(process.env.PNPM_HOME);
|
||||
expect(updated).not.toContain(path.join(process.env.PNPM_HOME, "bin"));
|
||||
} finally {
|
||||
realpathNative.mockRestore();
|
||||
}
|
||||
});
|
||||
|
||||
it.each([
|
||||
|
||||
@@ -50,6 +50,73 @@ function isKnownPathDir(existingPathParts: ReadonlySet<string>, dirPath: string)
|
||||
return existingPathParts.has(dirPath) || isDirectory(dirPath);
|
||||
}
|
||||
|
||||
function realpathExistingPath(candidate: string): string | undefined {
|
||||
const suffix: string[] = [];
|
||||
let current = candidate;
|
||||
while (true) {
|
||||
try {
|
||||
return path.resolve(fs.realpathSync.native(current), ...suffix.toReversed());
|
||||
} catch {
|
||||
const parent = path.dirname(current);
|
||||
if (parent === current) {
|
||||
return undefined;
|
||||
}
|
||||
suffix.push(path.basename(current));
|
||||
current = parent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isSameOrChildPath(candidate: string, parent: string): boolean {
|
||||
const relative = path.relative(parent, candidate);
|
||||
return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
|
||||
}
|
||||
|
||||
function isFilesystemRoot(dirPath: string): boolean {
|
||||
return path.dirname(dirPath) === dirPath;
|
||||
}
|
||||
|
||||
function normalizeTrustedPackageManagerRoot(params: {
|
||||
value: string | undefined;
|
||||
cwd: string | undefined;
|
||||
homeDir: string;
|
||||
}): string | undefined {
|
||||
const trimmed = params.value?.trim();
|
||||
if (!trimmed || !path.isAbsolute(trimmed)) {
|
||||
return undefined;
|
||||
}
|
||||
const normalized = path.normalize(trimmed);
|
||||
if (normalized === "/proc" || normalized.startsWith(`/proc${path.sep}`)) {
|
||||
return undefined;
|
||||
}
|
||||
if (!params.cwd) {
|
||||
return normalized;
|
||||
}
|
||||
|
||||
const cwd = path.resolve(params.cwd);
|
||||
const homeDir = path.resolve(params.homeDir);
|
||||
if (cwd === homeDir || isFilesystemRoot(cwd)) {
|
||||
return normalized;
|
||||
}
|
||||
if (isSameOrChildPath(normalized, cwd)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const realCandidate = realpathExistingPath(normalized);
|
||||
const realCwd = realpathExistingPath(cwd);
|
||||
const realHome = realpathExistingPath(homeDir);
|
||||
if (
|
||||
realCwd &&
|
||||
realCwd !== realHome &&
|
||||
!isFilesystemRoot(realCwd) &&
|
||||
realCandidate &&
|
||||
isSameOrChildPath(realCandidate, realCwd)
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
function isLinuxbrewPath(dirPath: string): boolean {
|
||||
return dirPath.split(path.sep).includes(".linuxbrew");
|
||||
}
|
||||
@@ -131,18 +198,38 @@ function candidateBinDirs(
|
||||
// This includes Brew/Homebrew dirs, which are useful for finding `openclaw`
|
||||
// in launchd/minimal environments but must not be treated as trusted.
|
||||
append.push(...resolvePathBootstrapBrewDirs({ homeDir, platform, existingPathParts }));
|
||||
const pnpmHome = normalizeTrustedPackageManagerRoot({
|
||||
value: process.env.PNPM_HOME,
|
||||
cwd,
|
||||
homeDir,
|
||||
});
|
||||
if (pnpmHome) {
|
||||
append.push(pnpmHome);
|
||||
append.push(path.join(pnpmHome, "bin"));
|
||||
}
|
||||
const npmPrefix = normalizeTrustedPackageManagerRoot({
|
||||
value: process.env.NPM_CONFIG_PREFIX,
|
||||
cwd,
|
||||
homeDir,
|
||||
});
|
||||
if (npmPrefix) {
|
||||
append.push(path.join(npmPrefix, "bin"));
|
||||
}
|
||||
const miseDataDir = process.env.MISE_DATA_DIR ?? path.join(homeDir, ".local", "share", "mise");
|
||||
const miseShims = path.join(miseDataDir, "shims");
|
||||
if (isKnownPathDir(existingPathParts, miseShims)) {
|
||||
append.push(miseShims);
|
||||
}
|
||||
if (platform === "darwin") {
|
||||
append.push(path.join(homeDir, "Library", "pnpm", "bin"));
|
||||
append.push(path.join(homeDir, "Library", "pnpm"));
|
||||
}
|
||||
if (process.env.XDG_BIN_HOME) {
|
||||
append.push(process.env.XDG_BIN_HOME);
|
||||
}
|
||||
append.push(path.join(homeDir, ".local", "bin"));
|
||||
append.push(path.join(homeDir, ".npm-global", "bin"));
|
||||
append.push(path.join(homeDir, ".local", "share", "pnpm", "bin"));
|
||||
append.push(path.join(homeDir, ".local", "share", "pnpm"));
|
||||
append.push(path.join(homeDir, ".bun", "bin"));
|
||||
append.push(path.join(homeDir, ".yarn", "bin"));
|
||||
|
||||
Reference in New Issue
Block a user