mirror of
https://github.com/oven-sh/bun
synced 2026-02-16 05:42:43 +00:00
fix(bunfig): expand tilde (~) in path settings
When users specify paths like `globalBinDir = "~/.bun/bin"` in their bunfig.toml, the tilde is now properly expanded to $HOME instead of being treated as a literal directory name. Affected settings: - install.globalDir - install.globalBinDir - install.cache.dir - install.cache (shorthand) Fixes #25766 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -670,13 +670,13 @@ pub const Bunfig = struct {
|
||||
|
||||
if (install_obj.get("globalDir")) |dir| {
|
||||
if (dir.asString(allocator)) |value| {
|
||||
install.global_dir = value;
|
||||
install.global_dir = bun.path.expandTilde(allocator, value) orelse value;
|
||||
}
|
||||
}
|
||||
|
||||
if (install_obj.get("globalBinDir")) |dir| {
|
||||
if (dir.asString(allocator)) |value| {
|
||||
install.global_bin_dir = value;
|
||||
install.global_bin_dir = bun.path.expandTilde(allocator, value) orelse value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -696,7 +696,7 @@ pub const Bunfig = struct {
|
||||
}
|
||||
|
||||
if (cache.asString(allocator)) |value| {
|
||||
install.cache_directory = value;
|
||||
install.cache_directory = bun.path.expandTilde(allocator, value) orelse value;
|
||||
break :load;
|
||||
}
|
||||
|
||||
@@ -715,7 +715,7 @@ pub const Bunfig = struct {
|
||||
|
||||
if (cache.get("dir")) |directory| {
|
||||
if (directory.asString(allocator)) |value| {
|
||||
install.cache_directory = value;
|
||||
install.cache_directory = bun.path.expandTilde(allocator, value) orelse value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2066,6 +2066,44 @@ pub fn posixToPlatformInPlace(comptime T: type, path_buffer: []T) void {
|
||||
}
|
||||
}
|
||||
|
||||
/// Expands a leading tilde (`~`) in a path to the user's home directory.
|
||||
/// Returns the original path if it doesn't start with `~` or if the home directory
|
||||
/// cannot be determined.
|
||||
///
|
||||
/// Examples:
|
||||
/// - `~/foo/bar` -> `/home/user/foo/bar`
|
||||
/// - `~` -> `/home/user`
|
||||
/// - `/absolute/path` -> `/absolute/path` (unchanged)
|
||||
/// - `relative/path` -> `relative/path` (unchanged)
|
||||
pub fn expandTilde(allocator: std.mem.Allocator, path: []const u8) ?[]const u8 {
|
||||
// Check if path starts with ~
|
||||
if (path.len == 0 or path[0] != '~') {
|
||||
return null; // No expansion needed
|
||||
}
|
||||
|
||||
// Handle ~ or ~/...
|
||||
// We only expand ~ followed by nothing or a path separator
|
||||
if (path.len == 1 or isSepAny(path[1])) {
|
||||
const home_dir = bun.env_var.HOME.get() orelse return null;
|
||||
|
||||
if (path.len == 1) {
|
||||
// Just "~"
|
||||
return allocator.dupe(u8, home_dir) catch return null;
|
||||
}
|
||||
|
||||
// "~/..." - join home_dir with the rest of the path (skip ~/)
|
||||
const rest = path[2..]; // Skip ~/
|
||||
const result = allocator.alloc(u8, home_dir.len + 1 + rest.len) catch return null;
|
||||
@memcpy(result[0..home_dir.len], home_dir);
|
||||
result[home_dir.len] = '/';
|
||||
@memcpy(result[home_dir.len + 1 ..], rest);
|
||||
return result;
|
||||
}
|
||||
|
||||
// ~username expansion is not supported, return null
|
||||
return null;
|
||||
}
|
||||
|
||||
const Fs = @import("../fs.zig");
|
||||
const std = @import("std");
|
||||
|
||||
|
||||
145
test/regression/issue/25766.test.ts
Normal file
145
test/regression/issue/25766.test.ts
Normal file
@@ -0,0 +1,145 @@
|
||||
// https://github.com/oven-sh/bun/issues/25766
|
||||
// Tilde expansion (~) should work in bunfig.toml path settings
|
||||
//
|
||||
// When users specify paths like `globalBinDir = "~/.bun/bin"` in their bunfig.toml,
|
||||
// Bun should expand the `~` to $HOME. Without the fix, it creates a literal folder named `~`.
|
||||
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { existsSync } from "fs";
|
||||
import { bunEnv, bunExe, tempDir } from "harness";
|
||||
import { join } from "path";
|
||||
|
||||
describe("bunfig.toml tilde expansion", () => {
|
||||
test("globalBinDir with tilde expands to home directory", async () => {
|
||||
// Create a fake home directory and a bunfig that uses tilde for globalBinDir
|
||||
using dir = tempDir("issue-25766-bin", {
|
||||
"fake-home/.bun/bin/.gitkeep": "",
|
||||
"package.json": JSON.stringify({
|
||||
name: "test-pkg",
|
||||
version: "1.0.0",
|
||||
}),
|
||||
"bunfig.toml": `[install]
|
||||
globalBinDir = "~/.bun/bin"
|
||||
`,
|
||||
});
|
||||
|
||||
const fakeHome = join(String(dir), "fake-home");
|
||||
|
||||
// Use `bun link` which triggers the openGlobalBinDir and openGlobalDir code paths
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "link"],
|
||||
cwd: String(dir),
|
||||
env: {
|
||||
...bunEnv,
|
||||
HOME: fakeHome,
|
||||
},
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
});
|
||||
|
||||
await proc.exited;
|
||||
|
||||
// The key assertion: a literal "~" directory should NOT be created in cwd
|
||||
// If the bug exists, it would create a directory literally named "~"
|
||||
const literalTildeDir = join(String(dir), "~");
|
||||
expect(existsSync(literalTildeDir)).toBe(false);
|
||||
});
|
||||
|
||||
test("globalDir with tilde expands to home directory", async () => {
|
||||
using dir = tempDir("issue-25766-global", {
|
||||
"fake-home/.bun/install/global/.gitkeep": "",
|
||||
"package.json": JSON.stringify({
|
||||
name: "test-pkg",
|
||||
version: "1.0.0",
|
||||
}),
|
||||
"bunfig.toml": `[install]
|
||||
globalDir = "~/.bun/install/global"
|
||||
`,
|
||||
});
|
||||
|
||||
const fakeHome = join(String(dir), "fake-home");
|
||||
|
||||
// Use `bun link` which triggers the openGlobalDir code path
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "link"],
|
||||
cwd: String(dir),
|
||||
env: {
|
||||
...bunEnv,
|
||||
HOME: fakeHome,
|
||||
},
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
});
|
||||
|
||||
await proc.exited;
|
||||
|
||||
// No literal "~" directory should be created
|
||||
const literalTildeDir = join(String(dir), "~");
|
||||
expect(existsSync(literalTildeDir)).toBe(false);
|
||||
});
|
||||
|
||||
test("cache.dir with tilde expands to home directory", async () => {
|
||||
using dir = tempDir("issue-25766-cache", {
|
||||
"fake-home/.bun/install/cache/.gitkeep": "",
|
||||
"package.json": JSON.stringify({
|
||||
name: "test-pkg",
|
||||
version: "1.0.0",
|
||||
}),
|
||||
"bunfig.toml": `[install.cache]
|
||||
dir = "~/.bun/install/cache"
|
||||
`,
|
||||
});
|
||||
|
||||
const fakeHome = join(String(dir), "fake-home");
|
||||
|
||||
// Regular install should trigger cache directory access
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "install"],
|
||||
cwd: String(dir),
|
||||
env: {
|
||||
...bunEnv,
|
||||
HOME: fakeHome,
|
||||
},
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
});
|
||||
|
||||
await proc.exited;
|
||||
|
||||
// No literal "~" directory should be created
|
||||
const literalTildeDir = join(String(dir), "~");
|
||||
expect(existsSync(literalTildeDir)).toBe(false);
|
||||
});
|
||||
|
||||
test("cache shorthand with tilde expands to home directory", async () => {
|
||||
using dir = tempDir("issue-25766-cache-short", {
|
||||
"fake-home/.bun/install/cache/.gitkeep": "",
|
||||
"package.json": JSON.stringify({
|
||||
name: "test-pkg",
|
||||
version: "1.0.0",
|
||||
}),
|
||||
"bunfig.toml": `[install]
|
||||
cache = "~/.bun/install/cache"
|
||||
`,
|
||||
});
|
||||
|
||||
const fakeHome = join(String(dir), "fake-home");
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "install"],
|
||||
cwd: String(dir),
|
||||
env: {
|
||||
...bunEnv,
|
||||
HOME: fakeHome,
|
||||
},
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
});
|
||||
|
||||
await proc.exited;
|
||||
|
||||
// No literal "~" directory should be created
|
||||
const literalTildeDir = join(String(dir), "~");
|
||||
expect(existsSync(literalTildeDir)).toBe(false);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user