fix(update): resolve Bun global directories before changing cwd

This commit is contained in:
morluto
2026-10-11 18:11:52 +08:00
parent 41544fb952
commit e90acaea67
3 changed files with 46 additions and 24 deletions
+17 -7
View File
@@ -1,4 +1,4 @@
import { join } from "node:path";
import { join, resolve } from "node:path";
import { describe, expect, it } from "vitest";
@@ -413,17 +413,17 @@ describe("Bun installation ownership", () => {
it("follows Bun's global directory precedence", () => {
const home = { HOME: "/home/user" };
expect(bunGlobalDirectory(home, "linux")).toBe(
join("/home/user", ".bun", "install", "global"),
resolve("/home/user", ".bun", "install", "global"),
);
expect(
bunGlobalDirectory({ ...home, XDG_CACHE_HOME: "/cache" }, "linux"),
).toBe(join("/cache", ".bun", "install", "global"));
).toBe(resolve("/cache", ".bun", "install", "global"));
expect(
bunGlobalDirectory(
{ ...home, XDG_CACHE_HOME: "/cache", BUN_INSTALL: "/opt/bun" },
"linux",
),
).toBe(join("/opt/bun", "install", "global"));
).toBe(resolve("/opt/bun", "install", "global"));
expect(
bunGlobalDirectory(
{
@@ -433,12 +433,22 @@ describe("Bun installation ownership", () => {
},
"linux",
),
).toBe("/srv/bun-global");
).toBe(resolve("/srv/bun-global"));
const profile = resolve("profile");
expect(
bunGlobalDirectory(
{ USERPROFILE: "C:\\Users\\user", HOME: "/c/Users/user" },
{ USERPROFILE: profile, HOME: resolve("other-home") },
"win32",
),
).toBe(join("C:\\Users\\user", ".bun", "install", "global"));
).toBe(join(profile, ".bun", "install", "global"));
});
it("resolves relative installation and cache roots before appending the global layout", () => {
expect(bunGlobalDirectory({ BUN_INSTALL: "bun" }, "linux")).toBe(
resolve("bun", "install", "global"),
);
expect(bunGlobalDirectory({ XDG_CACHE_HOME: "cache" }, "linux")).toBe(
resolve("cache", ".bun", "install", "global"),
);
});
});
+5 -3
View File
@@ -141,19 +141,21 @@ const nonemptyEnvironment = (value: string | undefined): string | undefined =>
* Bun's global project directory for the selected environment, in Bun's precedence:
* BUN_INSTALL_GLOBAL_DIR, then $BUN_INSTALL/install/global, then
* $XDG_CACHE_HOME/.bun/install/global, then ~/.bun/install/global.
* Resolve against the caller's cwd before reusing the path as a subprocess cwd.
*/
export const bunGlobalDirectory = (
environment: Readonly<NodeJS.ProcessEnv>,
platform: NodeJS.Platform,
): string => {
const explicit = nonemptyEnvironment(environment.BUN_INSTALL_GLOBAL_DIR);
if (explicit !== undefined) return explicit;
if (explicit !== undefined) return resolve(explicit);
const installRoot = nonemptyEnvironment(environment.BUN_INSTALL);
if (installRoot !== undefined) return join(installRoot, "install", "global");
if (installRoot !== undefined)
return resolve(installRoot, "install", "global");
const cacheRoot =
nonemptyEnvironment(environment.XDG_CACHE_HOME) ??
homeDirectoryFromEnvironment(environment, platform);
return join(cacheRoot, ".bun", "install", "global");
return resolve(cacheRoot, ".bun", "install", "global");
};
/**
+24 -14
View File
@@ -1,9 +1,9 @@
import { mkdir, readFile, rm, writeFile, access } from "node:fs/promises";
import { delimiter, dirname, join, resolve } from "node:path";
import { delimiter, dirname, join, relative, resolve } from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { err, ok } from "../../src/domain/result.js";
import { runUpdate } from "../../src/application/Update.js";
import { runUpdate, type UpdateResult } from "../../src/application/Update.js";
import { PRODUCT_IDENTITY } from "../../src/identity.js";
import { supportedClients } from "../../src/application/SupportedClients.js";
import {
@@ -375,14 +375,15 @@ describe("update subprocess boundaries", () => {
describe("Bun global installation update", () => {
it.each([
["1.4.2", "updated"],
["1.2.17", "updated"],
["1.2.14", "failed"],
["1.2.17-canary.1", "failed"],
["unknown", "failed"],
["1.4.2", "updated", "absolute"],
["1.2.17", "updated", "absolute"],
["1.2.17", "updated", "relative"],
["1.2.14", "failed", "absolute"],
["1.2.17-canary.1", "failed", "absolute"],
["unknown", "failed", "absolute"],
])(
"handles Bun %s with outcome %s inside its global project",
async (bunVersion, status) => {
"handles Bun %s with outcome %s using %s global-directory configuration",
async (bunVersion, status, directoryMode) => {
// A recording `bun` on PATH crosses the real subprocess boundary without registry access.
const root = await createTestTempDirectory("rea-update-bun-");
const globalDirectory = join(root, "bun", "install", "global");
@@ -440,12 +441,21 @@ describe("Bun global installation update", () => {
BUN_INSTALL: join(root, "bun"),
};
delete environment.BUN_INSTALL_GLOBAL_DIR;
if (directoryMode === "relative")
environment.BUN_INSTALL_GLOBAL_DIR = relative(root, globalDirectory);
const result = await runUpdate(
"3.2.1",
systemUpdateHost(packageRoot, home, environment),
"structured",
);
const callerDirectory = process.cwd();
let result: UpdateResult;
try {
process.chdir(root);
result = await runUpdate(
"3.2.1",
systemUpdateHost(packageRoot, home, environment),
"structured",
);
} finally {
process.chdir(callerDirectory);
}
const invocations = (await readFile(logPath, "utf8"))
.trim()