



















@@ -4,78 +4,114 @@ import { describe, expect, it } from "vitest";
44import { withTempDir } from "../test-helpers/temp-dir.js";
55import { resolveBrewExecutable, resolveBrewPathDirs } from "./brew.js";
667+const HOMEBREW_ENV_KEYS = ["HOMEBREW_BREW_FILE", "HOMEBREW_PREFIX"] as const;
8+79describe("brew helpers", () => {
810async function writeExecutable(filePath: string) {
911await fs.mkdir(path.dirname(filePath), { recursive: true });
1012await fs.writeFile(filePath, "#!/bin/sh\necho ok\n", "utf-8");
1113await fs.chmod(filePath, 0o755);
1214}
131516+async function withHomebrewEnv(
17+values: Partial<Record<(typeof HOMEBREW_ENV_KEYS)[number], string>>,
18+run: () => Promise<void>,
19+) {
20+const previous = Object.fromEntries(
21+HOMEBREW_ENV_KEYS.map((key) => [key, process.env[key]]),
22+) as Record<(typeof HOMEBREW_ENV_KEYS)[number], string | undefined>;
23+try {
24+for (const key of HOMEBREW_ENV_KEYS) {
25+const value = values[key];
26+if (value === undefined) {
27+delete process.env[key];
28+} else {
29+process.env[key] = value;
30+}
31+}
32+await run();
33+} finally {
34+for (const key of HOMEBREW_ENV_KEYS) {
35+const value = previous[key];
36+if (value === undefined) {
37+delete process.env[key];
38+} else {
39+process.env[key] = value;
40+}
41+}
42+}
43+}
44+45+async function withPathEnv(value: string | undefined, run: () => Promise<void>) {
46+const previous = process.env.PATH;
47+try {
48+if (value === undefined) {
49+delete process.env.PATH;
50+} else {
51+process.env.PATH = value;
52+}
53+await run();
54+} finally {
55+if (previous === undefined) {
56+delete process.env.PATH;
57+} else {
58+process.env.PATH = previous;
59+}
60+}
61+}
62+1463it("resolves brew from ~/.linuxbrew/bin when executable exists", async () => {
1564await withTempDir({ prefix: "openclaw-brew-" }, async (tmp) => {
1665const homebrewBin = path.join(tmp, ".linuxbrew", "bin");
1766const brewPath = path.join(homebrewBin, "brew");
1867await writeExecutable(brewPath);
196820-const env: NodeJS.ProcessEnv = {};
21-expect(resolveBrewExecutable({ homeDir: tmp, env })).toBe(brewPath);
69+await withPathEnv("", async () => {
70+expect(resolveBrewExecutable({ homeDir: tmp })).toBe(brewPath);
71+});
2272});
2373});
247425-it("prefers HOMEBREW_PREFIX/bin/brew when present", async () => {
75+it("resolves brew from absolute PATH entries for non-standard installs", async () => {
2676await withTempDir({ prefix: "openclaw-brew-" }, async (tmp) => {
27-const prefix = path.join(tmp, "prefix");
28-const prefixBin = path.join(prefix, "bin");
29-const prefixBrew = path.join(prefixBin, "brew");
30-await writeExecutable(prefixBrew);
31-32-const homebrewBin = path.join(tmp, ".linuxbrew", "bin");
33-const homebrewBrew = path.join(homebrewBin, "brew");
34-await writeExecutable(homebrewBrew);
77+const customBin = path.join(tmp, "custom-homebrew", "bin");
78+const customBrew = path.join(customBin, "brew");
79+await writeExecutable(customBrew);
358036-const env: NodeJS.ProcessEnv = { HOMEBREW_PREFIX: prefix };
37-expect(resolveBrewExecutable({ homeDir: tmp, env })).toBe(prefixBrew);
81+await withPathEnv(customBin, async () => {
82+expect(resolveBrewExecutable({ homeDir: path.join(tmp, "home") })).toBe(customBrew);
83+});
3884});
3985});
408641-it("prefers HOMEBREW_BREW_FILE over prefix and trims value", async () => {
87+it("ignores HOMEBREW_BREW_FILE and HOMEBREW_PREFIX by default", async () => {
4288await withTempDir({ prefix: "openclaw-brew-" }, async (tmp) => {
4389const explicit = path.join(tmp, "custom", "brew");
4490const prefix = path.join(tmp, "prefix");
45-const prefixBrew = path.join(prefix, "bin", "brew");
91+const prefixBin = path.join(prefix, "bin");
92+const prefixBrew = path.join(prefixBin, "brew");
93+const homebrewBin = path.join(tmp, ".linuxbrew", "bin");
94+const homebrewBrew = path.join(homebrewBin, "brew");
4695await writeExecutable(explicit);
4796await writeExecutable(prefixBrew);
97+await writeExecutable(homebrewBrew);
489849-const env: NodeJS.ProcessEnv = {
50-HOMEBREW_BREW_FILE: ` ${explicit} `,
51-HOMEBREW_PREFIX: prefix,
52-};
53-expect(resolveBrewExecutable({ homeDir: tmp, env })).toBe(explicit);
54-});
55-});
56-57-it("falls back to prefix when HOMEBREW_BREW_FILE is missing or not executable", async () => {
58-await withTempDir({ prefix: "openclaw-brew-" }, async (tmp) => {
59-const explicit = path.join(tmp, "custom", "brew");
60-const prefix = path.join(tmp, "prefix");
61-const prefixBrew = path.join(prefix, "bin", "brew");
62-let brewFile = explicit;
63-if (process.platform === "win32") {
64-// Windows doesn't enforce POSIX executable bits, so use a missing path
65-// to verify fallback behavior deterministically.
66-brewFile = path.join(tmp, "custom", "missing-brew");
67-} else {
68-await fs.mkdir(path.dirname(explicit), { recursive: true });
69-await fs.writeFile(explicit, "#!/bin/sh\necho no\n", "utf-8");
70-await fs.chmod(explicit, 0o644);
71-}
72-await writeExecutable(prefixBrew);
73-74-const env: NodeJS.ProcessEnv = {
75-HOMEBREW_BREW_FILE: brewFile,
76-HOMEBREW_PREFIX: prefix,
77-};
78-expect(resolveBrewExecutable({ homeDir: tmp, env })).toBe(prefixBrew);
99+await withHomebrewEnv(
100+{
101+HOMEBREW_BREW_FILE: explicit,
102+HOMEBREW_PREFIX: prefix,
103+},
104+async () => {
105+const env: NodeJS.ProcessEnv = {
106+HOMEBREW_BREW_FILE: explicit,
107+HOMEBREW_PREFIX: prefix,
108+};
109+await withPathEnv("", async () => {
110+expect(resolveBrewExecutable({ homeDir: tmp, env })).toBe(homebrewBrew);
111+});
112+expect(resolveBrewPathDirs({ homeDir: tmp, env })).not.toContain(prefixBin);
113+},
114+);
79115});
80116});
81117@@ -85,37 +121,39 @@ describe("brew helpers", () => {
85121const brewPath = path.join(homebrewBin, "brew");
86122await writeExecutable(brewPath);
8712388-const env: NodeJS.ProcessEnv = {
89-HOMEBREW_BREW_FILE: " ",
90-HOMEBREW_PREFIX: "\t",
91-};
92-93-expect(resolveBrewExecutable({ homeDir: tmp, env })).toBe(brewPath);
94-95-const dirs = resolveBrewPathDirs({ homeDir: tmp, env });
96-expect(dirs).not.toContain(path.join("", "bin"));
97-expect(dirs).not.toContain(path.join("", "sbin"));
124+await withHomebrewEnv(
125+{
126+HOMEBREW_BREW_FILE: " ",
127+HOMEBREW_PREFIX: "\t",
128+},
129+async () => {
130+await withPathEnv("", async () => {
131+expect(resolveBrewExecutable({ homeDir: tmp })).toBe(brewPath);
132+});
133+134+const dirs = resolveBrewPathDirs({ homeDir: tmp });
135+expect(dirs).not.toContain(path.join("", "bin"));
136+expect(dirs).not.toContain(path.join("", "sbin"));
137+},
138+);
98139});
99140});
100141101142it("always includes the standard macOS brew dirs after linuxbrew candidates", () => {
102-const env: NodeJS.ProcessEnv = {
103-HOMEBREW_BREW_FILE: " ",
104-HOMEBREW_PREFIX: " ",
105-};
106-const dirs = resolveBrewPathDirs({ homeDir: "/home/test", env });
143+const dirs = resolveBrewPathDirs({ homeDir: "/home/test" });
107144108145expect(dirs.slice(-2)).toEqual(["/opt/homebrew/bin", "/usr/local/bin"]);
109146});
110147111-it("includes Linuxbrew bin/sbin in path candidates", () => {
112-const env: NodeJS.ProcessEnv = { HOMEBREW_PREFIX: "/custom/prefix" };
113-const dirs = resolveBrewPathDirs({ homeDir: "/home/test", env });
114-expect(dirs).toContain(path.join("/custom/prefix", "bin"));
115-expect(dirs).toContain(path.join("/custom/prefix", "sbin"));
116-expect(dirs).toContain("/home/linuxbrew/.linuxbrew/bin");
117-expect(dirs).toContain("/home/linuxbrew/.linuxbrew/sbin");
118-expect(dirs).toContain(path.join("/home/test", ".linuxbrew", "bin"));
119-expect(dirs).toContain(path.join("/home/test", ".linuxbrew", "sbin"));
148+it("includes Linuxbrew bin/sbin in path candidates without env prefixes", async () => {
149+await withHomebrewEnv({ HOMEBREW_PREFIX: "/custom/prefix" }, async () => {
150+const dirs = resolveBrewPathDirs({ homeDir: "/home/test" });
151+expect(dirs).not.toContain(path.join("/custom/prefix", "bin"));
152+expect(dirs).not.toContain(path.join("/custom/prefix", "sbin"));
153+expect(dirs).toContain("/home/linuxbrew/.linuxbrew/bin");
154+expect(dirs).toContain("/home/linuxbrew/.linuxbrew/sbin");
155+expect(dirs).toContain(path.join("/home/test", ".linuxbrew", "bin"));
156+expect(dirs).toContain(path.join("/home/test", ".linuxbrew", "sbin"));
157+});
120158});
121159});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。