Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 13 additions & 12 deletions src/bunfig/arguments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,23 @@ use crate::bunfig::Bunfig;

// ─── bunfig loading ──────────────────────────────────────────────────────────

/// `$XDG_CONFIG_HOME/.bunfig.toml` when that file exists, otherwise
/// `$HOME/.bunfig.toml` (same rule as the user-level `.npmrc` in
/// `PackageManager::init`). Many desktops and CI runners export
/// `XDG_CONFIG_HOME` without ever putting a bunfig there.
fn get_home_config_path(buf: &mut PathBuffer) -> Option<&ZStr> {
let paths: [&[u8]; 1] = [b".bunfig.toml"];

if let Some(data_dir) = env_var::XDG_CONFIG_HOME.get() {
return Some(resolve_path::join_abs_string_buf_z::<platform::Auto>(
data_dir, &mut **buf, &paths,
));
}

if let Some(home_dir) = env_var::HOME.get() {
return Some(resolve_path::join_abs_string_buf_z::<platform::Auto>(
home_dir, &mut **buf, &paths,
));
}
let xdg_dir = env_var::XDG_CONFIG_HOME.get_not_empty().filter(|xdg_dir| {
bun_sys::exists_z(resolve_path::join_abs_string_buf_z::<platform::Auto>(
xdg_dir, &mut **buf, &paths,
))
});
let dir = xdg_dir.or_else(|| env_var::HOME.get_not_empty())?;

None
Some(resolve_path::join_abs_string_buf_z::<platform::Auto>(
dir, &mut **buf, &paths,
))
}

fn load_bunfig(
Expand Down
63 changes: 62 additions & 1 deletion test/cli/install/npmrc.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { write } from "bun";
import { afterAll, beforeAll, describe, expect, it, test } from "bun:test";
import { rm } from "fs/promises";
import { VerdaccioRegistry, bunExe, bunEnv as env, tempDir } from "harness";
import { join } from "path";
import { basename, join } from "path";
const { iniInternals } = require("bun:internal-for-testing");
const { loadNpmrc } = iniInternals;

Expand Down Expand Up @@ -224,6 +224,67 @@ registry = http://localhost:${registry.port}/
});
});

// The global .bunfig.toml is looked up with the same rule as the user .npmrc above.
describe("global .bunfig.toml lookup", () => {
const bunfig = (cacheDir: string) => `[install]\ncache = "${cacheDir}"\n`;
const pkg = { "pkg/package.json": JSON.stringify({ name: "bunfig-lookup", version: "0.0.1" }) };

// `bun pm cache` prints the install cache directory, which each candidate
// .bunfig.toml points at a differently named directory, so its output shows
// which file was read. BUN_INSTALL_CACHE_DIR would take precedence over
// bunfig, and CI runners set it as well as XDG_CONFIG_HOME, so both are removed.
async function pmCache(dir: string, envOverride: Record<string, string>) {
const spawnEnv = { ...env, HOME: join(dir, "home"), USERPROFILE: join(dir, "home") };
delete spawnEnv.XDG_CONFIG_HOME;
delete spawnEnv.BUN_INSTALL_CACHE_DIR;

await using proc = Bun.spawn({
cmd: [bunExe(), "pm", "cache"],
cwd: join(dir, "pkg"),
env: { ...spawnEnv, ...envOverride },
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
return { cacheDir: basename(stdout.trim()), stderr, exitCode };
}

const usesCacheDir = (cacheDir: string) => ({ cacheDir, stderr: "", exitCode: 0 });

it.concurrent("uses $XDG_CONFIG_HOME/.bunfig.toml when it exists", async () => {
using dir = tempDir("bunfig-xdg", {
...pkg,
"home/.bunfig.toml": bunfig("home-cache"),
"xdg/.bunfig.toml": bunfig("xdg-cache"),
});
const result = await pmCache(String(dir), { XDG_CONFIG_HOME: join(String(dir), "xdg") });
expect(result).toEqual(usesCacheDir("xdg-cache"));
});

// https://github.com/oven-sh/bun/issues/23128
it.concurrent("falls back to $HOME/.bunfig.toml when $XDG_CONFIG_HOME has no .bunfig.toml", async () => {
using dir = tempDir("bunfig-xdg-without-bunfig", {
...pkg,
"home/.bunfig.toml": bunfig("home-cache"),
"xdg/.keep": "",
});
const result = await pmCache(String(dir), { XDG_CONFIG_HOME: join(String(dir), "xdg") });
expect(result).toEqual(usesCacheDir("home-cache"));
});

it.concurrent("uses $HOME/.bunfig.toml when $XDG_CONFIG_HOME is unset", async () => {
using dir = tempDir("bunfig-xdg-unset", { ...pkg, "home/.bunfig.toml": bunfig("home-cache") });
const result = await pmCache(String(dir), {});
expect(result).toEqual(usesCacheDir("home-cache"));
});

it.concurrent("uses $HOME/.bunfig.toml when $XDG_CONFIG_HOME is empty", async () => {
using dir = tempDir("bunfig-xdg-empty", { ...pkg, "home/.bunfig.toml": bunfig("home-cache") });
const result = await pmCache(String(dir), { XDG_CONFIG_HOME: "" });
expect(result).toEqual(usesCacheDir("home-cache"));
});
});

it("package config overrides home config", async () => {
const { packageDir, packageJson } = await registry.createTestDir();

Expand Down