Compare commits

...

9 Commits

Author SHA1 Message Date
Jarred Sumner
92e0dd8a8d Merge branch 'main' into claude/fix-home-npmrc-reading 2025-10-04 06:50:51 -07:00
Claude Bot
bd599ad8f5 Add detailed comments explaining .npmrc loading matches npm's behavior
References npm's implementation (workspaces/config/lib/index.js) with specific
line numbers to document that Bun's .npmrc loading order and precedence rules
match npm's:

- npm's priority chain: default → builtin → global → user → project → env → cli
- Project config overrides user config
- User config from HOME/.npmrc or XDG_CONFIG_HOME/.npmrc
- Project config from CWD/.npmrc

The comments cite exact line numbers from npm's implementation for:
- Config priority chain (line 119-124)
- Config loading (line 240-272)
- loadUserConfig (line 259)
- loadProjectConfig (line 256)
- loadHome (line 317-319)
2025-10-04 10:32:08 +00:00
Claude Bot
c311e6afb9 Remove redundant beforeAll hook in nested describe block
The outer beforeAll already starts the registry for all tests in the file,
including nested describe blocks. The redundant beforeAll was attempting to
call registry.start() again, which caused the test to timeout during cleanup.
2025-10-04 10:20:09 +00:00
Claude Bot
2589ee3fcb Fix beforeAll hook to properly check if registry is started 2025-10-04 10:18:55 +00:00
Claude Bot
615bdc22f1 Fix env variable handling in HOME .npmrc test
Previously, the test was setting BUN_INSTALL_CACHE_DIR: undefined which
passes the literal string "undefined" to the child process. This fix
properly sets BUN_INSTALL_CACHE_DIR to a temporary directory within the
test directory to isolate the test from the user's actual cache.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-02 02:12:23 +00:00
Claude Bot
185a2cb236 Fix HOME directory .npmrc tests for Windows compatibility
The tests were only setting the HOME environment variable, but on Windows,
Bun uses USERPROFILE to determine the home directory. This caused tests to
fail on Windows CI because the HOME .npmrc file wasn't being found.

This change updates all HOME directory .npmrc tests to set both HOME and
USERPROFILE environment variables, ensuring cross-platform compatibility.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-30 22:05:53 +00:00
Claude Bot
5f3f989301 Fix .npmrc loading to always load both HOME and CWD .npmrc files
Previously, the if-else structure meant that we would load either:
- HOME/.npmrc + CWD/.npmrc (when HOME is set)
- CWD/.npmrc only (when HOME is not set)

This refactors the code to always load both files when HOME is set, ensuring proper configuration cascading where:
1. HOME/.npmrc is loaded first (if HOME/XDG_CONFIG_HOME is set)
2. CWD/.npmrc is loaded second and overrides HOME settings

The change simplifies the logic by building a list of .npmrc paths to load and passing them all to `loadNpmrcConfig` in a single call.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-30 06:15:49 +00:00
Jarred Sumner
c901510c10 Merge branch 'main' into claude/fix-home-npmrc-reading 2025-09-29 15:29:33 -07:00
Claude Bot
82bc3a8792 Fix .npmrc path construction for HOME directory
Previously, the code was using "./.npmrc" when constructing the path to the HOME directory .npmrc file, which resulted in malformed paths like "/home/user/./.npmrc". This fix changes it to ".npmrc" to correctly join with the base directory path.

Also adds comprehensive tests for HOME directory .npmrc reading, including:
- Reading .npmrc from modified HOME environment variable
- XDG_CONFIG_HOME support verification
- Project .npmrc override behavior
- Environment variable expansion in HOME .npmrc
- Graceful handling of missing HOME .npmrc

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-29 14:25:58 +00:00
2 changed files with 298 additions and 23 deletions

View File

@@ -788,31 +788,52 @@ pub fn init(
try env.load(entries_option.entries, &[_][]u8{}, .production, false);
initializeStore();
if (bun.getenvZ("XDG_CONFIG_HOME") orelse bun.getenvZ(bun.DotEnv.home_env)) |data_dir| {
var buf: bun.PathBuffer = undefined;
var parts = [_]string{
"./.npmrc",
};
bun.ini.loadNpmrcConfig(ctx.allocator, ctx.install orelse brk: {
const install_ = bun.handleOom(ctx.allocator.create(Api.BunInstall));
install_.* = std.mem.zeroes(Api.BunInstall);
ctx.install = install_;
break :brk install_;
}, env, true, &[_][:0]const u8{ Path.joinAbsStringBufZ(
data_dir,
&buf,
&parts,
.auto,
), ".npmrc" });
} else {
bun.ini.loadNpmrcConfig(ctx.allocator, ctx.install orelse brk: {
const install_ = bun.handleOom(ctx.allocator.create(Api.BunInstall));
install_.* = std.mem.zeroes(Api.BunInstall);
ctx.install = install_;
break :brk install_;
}, env, true, &[_][:0]const u8{".npmrc"});
// Load .npmrc files following npm's configuration precedence rules.
// See: https://github.com/npm/cli/blob/latest/workspaces/config/lib/index.js
//
// npm's configuration priority chain (line 119-124) from lowest to highest precedence:
// default → builtin → global → user → project → env → cli
//
// This means:
// - Project config (.npmrc in project root) overrides user config
// - User config (~/.npmrc) overrides global config
// - etc.
//
// npm's Config.load() (lines 240-272) loads these configs:
// - loadUserConfig() at line 259: loads ~/.npmrc (or userconfig path)
// - loadProjectConfig() at line 256: loads .npmrc in project root
//
// We load user config first, then project config. Since each config file's
// values overwrite previous values, this implements the correct precedence
// (project overrides user).
//
// npm's loadHome() (line 317-319): HOME = env.HOME || homedir()
// We also support XDG_CONFIG_HOME following XDG Base Directory specification
var npmrc_paths_buf: [2][:0]const u8 = undefined;
var npmrc_path_count: usize = 0;
// Load user config from HOME/.npmrc or XDG_CONFIG_HOME/.npmrc
// This matches npm's loadUserConfig() behavior (line 754-756)
var home_npmrc_buf: bun.PathBuffer = undefined;
if (bun.getenvZ("XDG_CONFIG_HOME") orelse bun.getenvZ(bun.DotEnv.home_env)) |data_dir| {
var parts = [_]string{".npmrc"};
npmrc_paths_buf[npmrc_path_count] = Path.joinAbsStringBufZ(data_dir, &home_npmrc_buf, &parts, .auto);
npmrc_path_count += 1;
}
// Load project config from CWD/.npmrc
// This matches npm's loadProjectConfig() behavior (line 653-683)
// Project config is loaded after user config and takes precedence
npmrc_paths_buf[npmrc_path_count] = ".npmrc";
npmrc_path_count += 1;
bun.ini.loadNpmrcConfig(ctx.allocator, ctx.install orelse brk: {
const install_ = bun.handleOom(ctx.allocator.create(Api.BunInstall));
install_.* = std.mem.zeroes(Api.BunInstall);
ctx.install = install_;
break :brk install_;
}, env, true, npmrc_paths_buf[0..npmrc_path_count]);
const cpu_count = bun.getThreadCount();
const options = Options{

View File

@@ -445,3 +445,257 @@ ${Object.keys(opts)
},
);
});
describe("HOME directory .npmrc", () => {
test("reads .npmrc from HOME when HOME env is modified", async () => {
const { packageDir } = await registry.createTestDir();
const customHome = join(packageDir, "custom_home");
await Bun.$`mkdir -p ${customHome}`;
await Bun.$`rm -rf ${packageDir}/bunfig.toml`;
// Create .npmrc in custom HOME
const homeNpmrc = `
registry=http://localhost:${registry.port}/
cache=${customHome}/.npm-cache
`;
await write(join(customHome, ".npmrc"), homeNpmrc);
// Create package.json
await write(
join(packageDir, "package.json"),
JSON.stringify({
name: "test-home-npmrc",
version: "1.0.0",
dependencies: {
"no-deps": "1.0.0",
},
}),
);
// Run install with modified HOME
const childEnv = { ...env };
childEnv.BUN_INSTALL_CACHE_DIR = join(packageDir, ".bun-cache");
childEnv.HOME = customHome;
childEnv.USERPROFILE = customHome; // Windows uses USERPROFILE instead of HOME
const { stdout, stderr, exited } = Bun.spawn({
cmd: [bunExe(), "install"],
cwd: packageDir,
env: childEnv,
stdout: "pipe",
stderr: "pipe",
});
const err = stderrForInstall(await stderr.text());
expect(err).not.toContain("error:");
expect(err).not.toContain("panic:");
expect(await exited).toBe(0);
// Verify package was installed
const nodeModules = join(packageDir, "node_modules");
expect(await Bun.file(join(nodeModules, "no-deps", "package.json")).exists()).toBeTrue();
});
test("reads .npmrc from XDG_CONFIG_HOME when set", async () => {
const { packageDir } = await registry.createTestDir();
const xdgConfigHome = join(packageDir, "xdg_config");
await Bun.$`mkdir -p ${xdgConfigHome}`;
await Bun.$`rm -rf ${packageDir}/bunfig.toml`;
// Create .npmrc in XDG_CONFIG_HOME
const xdgNpmrc = `
registry=http://localhost:${registry.port}/
`;
await write(join(xdgConfigHome, ".npmrc"), xdgNpmrc);
// Create package.json
await write(
join(packageDir, "package.json"),
JSON.stringify({
name: "test-xdg-npmrc",
version: "1.0.0",
dependencies: {
"no-deps": "1.0.0",
},
}),
);
// Run install with XDG_CONFIG_HOME set
const { stdout, stderr, exited } = Bun.spawn({
cmd: [bunExe(), "install"],
cwd: packageDir,
env: {
...env,
XDG_CONFIG_HOME: xdgConfigHome,
},
stdout: "pipe",
stderr: "pipe",
});
const err = stderrForInstall(await stderr.text());
expect(err).not.toContain("error:");
expect(err).not.toContain("panic:");
expect(await exited).toBe(0);
// Verify package was installed
const nodeModules = join(packageDir, "node_modules");
expect(await Bun.file(join(nodeModules, "no-deps", "package.json")).exists()).toBeTrue();
});
test("project .npmrc overrides HOME .npmrc", async () => {
const { packageDir } = await registry.createTestDir();
const customHome = join(packageDir, "custom_home");
await Bun.$`mkdir -p ${customHome}`;
await Bun.$`rm -rf ${packageDir}/bunfig.toml`;
// Create .npmrc in custom HOME with wrong registry
const homeNpmrc = `
registry=http://wrong-registry.example.com/
`;
await write(join(customHome, ".npmrc"), homeNpmrc);
// Create project .npmrc with correct registry
const projectNpmrc = `
registry=http://localhost:${registry.port}/
`;
await write(join(packageDir, ".npmrc"), projectNpmrc);
// Create package.json
await write(
join(packageDir, "package.json"),
JSON.stringify({
name: "test-npmrc-override",
version: "1.0.0",
dependencies: {
"no-deps": "1.0.0",
},
}),
);
// Run install with modified HOME
const { stdout, stderr, exited } = Bun.spawn({
cmd: [bunExe(), "install"],
cwd: packageDir,
env: {
...env,
HOME: customHome,
USERPROFILE: customHome, // Windows uses USERPROFILE instead of HOME
},
stdout: "pipe",
stderr: "pipe",
});
const err = stderrForInstall(await stderr.text());
expect(err).not.toContain("error:");
expect(err).not.toContain("panic:");
expect(await exited).toBe(0);
// Verify package was installed from correct registry
const nodeModules = join(packageDir, "node_modules");
expect(await Bun.file(join(nodeModules, "no-deps", "package.json")).exists()).toBeTrue();
});
test("expands environment variables from HOME .npmrc", async () => {
const { packageDir } = await registry.createTestDir();
const customHome = join(packageDir, "custom_home");
const token = await registry.generateUser("home_user", "secure123");
await Bun.$`mkdir -p ${customHome}`;
await Bun.$`rm -rf ${packageDir}/bunfig.toml`;
// Create .npmrc in custom HOME with env var
const homeNpmrc = `
registry=http://localhost:${registry.port}/
//localhost:${registry.port}/:_authToken=\${NPM_TOKEN}
`;
await write(join(customHome, ".npmrc"), homeNpmrc);
// Create package.json
await write(
join(packageDir, "package.json"),
JSON.stringify({
name: "test-env-expansion",
version: "1.0.0",
dependencies: {
"@needs-auth/test-pkg": "1.0.0",
},
}),
);
// Run install with env vars
const { stdout, stderr, exited } = Bun.spawn({
cmd: [bunExe(), "install"],
cwd: packageDir,
env: {
...env,
HOME: customHome,
USERPROFILE: customHome, // Windows uses USERPROFILE instead of HOME
NPM_TOKEN: token,
},
stdout: "pipe",
stderr: "pipe",
});
const err = stderrForInstall(await stderr.text());
expect(err).not.toContain("error:");
expect(err).not.toContain("panic:");
expect(await exited).toBe(0);
// Verify authenticated package was installed
const nodeModules = join(packageDir, "node_modules");
expect(await Bun.file(join(nodeModules, "@needs-auth", "test-pkg", "package.json")).exists()).toBeTrue();
});
test("handles missing HOME .npmrc gracefully", async () => {
const { packageDir } = await registry.createTestDir();
const customHome = join(packageDir, "empty_home");
await Bun.$`mkdir -p ${customHome}`;
await Bun.$`rm -rf ${packageDir}/bunfig.toml`;
// No .npmrc in HOME
// Create project .npmrc
const projectNpmrc = `
registry=http://localhost:${registry.port}/
`;
await write(join(packageDir, ".npmrc"), projectNpmrc);
// Create package.json
await write(
join(packageDir, "package.json"),
JSON.stringify({
name: "test-missing-home-npmrc",
version: "1.0.0",
dependencies: {
"no-deps": "1.0.0",
},
}),
);
// Run install with modified HOME (no .npmrc there)
const { stdout, stderr, exited } = Bun.spawn({
cmd: [bunExe(), "install"],
cwd: packageDir,
env: {
...env,
HOME: customHome,
USERPROFILE: customHome, // Windows uses USERPROFILE instead of HOME
},
stdout: "pipe",
stderr: "pipe",
});
const err = stderrForInstall(await stderr.text());
expect(err).not.toContain("error:");
expect(err).not.toContain("panic:");
expect(await exited).toBe(0);
// Verify package was installed
const nodeModules = join(packageDir, "node_modules");
expect(await Bun.file(join(nodeModules, "no-deps", "package.json")).exists()).toBeTrue();
});
});