diff --git a/src/ini.zig b/src/ini.zig index bcdb70e16b4d6..96229711f4c22 100644 --- a/src/ini.zig +++ b/src/ini.zig @@ -574,7 +574,7 @@ pub const IniTestingAPIs = struct { const install = allocator.create(bun.Schema.Api.BunInstall) catch bun.outOfMemory(); install.* = std.mem.zeroes(bun.Schema.Api.BunInstall); - loadNpmrc(allocator, install, env, false, &log, &source) catch { + loadNpmrc(allocator, install, env, false, ".npmrc", &log, &source) catch { return log.toJS(globalThis, allocator, "error"); }; @@ -878,35 +878,36 @@ pub fn loadNpmrcFromFile( install: *bun.Schema.Api.BunInstall, env: *bun.DotEnv.Loader, auto_loaded: bool, + npmrc_path: [:0]const u8, ) void { var log = bun.logger.Log.init(allocator); defer log.deinit(); - const npmrc_file = switch (bun.sys.openat(bun.FD.cwd(), ".npmrc", bun.O.RDONLY, 0)) { + const npmrc_file = switch (bun.sys.openat(bun.FD.cwd(), npmrc_path, bun.O.RDONLY, 0)) { .result => |fd| fd, .err => |err| { if (auto_loaded) return; Output.prettyErrorln("{}\nwhile opening .npmrc \"{s}\"", .{ err, - ".npmrc", + npmrc_path, }); Global.exit(1); }, }; defer _ = bun.sys.close(npmrc_file); - const source = switch (bun.sys.File.toSource(".npmrc", allocator)) { + const source = switch (bun.sys.File.toSource(npmrc_path, allocator)) { .result => |s| s, .err => |e| { Output.prettyErrorln("{}\nwhile reading .npmrc \"{s}\"", .{ e, - ".npmrc", + npmrc_path, }); Global.exit(1); }, }; defer allocator.free(source.contents); - loadNpmrc(allocator, install, env, auto_loaded, &log, &source) catch { + loadNpmrc(allocator, install, env, auto_loaded, npmrc_path, &log, &source) catch { if (log.errors == 1) Output.warn("Encountered an error while reading .npmrc:\n", .{}) else @@ -920,10 +921,11 @@ pub fn loadNpmrc( install: *bun.Schema.Api.BunInstall, env: *bun.DotEnv.Loader, auto_loaded: bool, + npmrc_path: [:0]const u8, log: *bun.logger.Log, source: *const bun.logger.Source, ) !void { - var parser = bun.ini.Parser.init(allocator, ".npmrc", source.contents, env); + var parser = bun.ini.Parser.init(allocator, npmrc_path, source.contents, env); defer parser.deinit(); parser.parse(parser.arena.allocator()) catch |e| { if (e == error.ParserError) { @@ -933,13 +935,13 @@ pub fn loadNpmrc( if (auto_loaded) { Output.warn("{}\nwhile reading .npmrc \"{s}\"", .{ e, - ".npmrc", + npmrc_path, }); return; } Output.prettyErrorln("{}\nwhile reading .npmrc \"{s}\"", .{ e, - ".npmrc", + npmrc_path, }); Global.exit(1); }; diff --git a/src/install/install.zig b/src/install/install.zig index e49e57fcfa385..72efc9d97bdfa 100644 --- a/src/install/install.zig +++ b/src/install/install.zig @@ -8492,6 +8492,31 @@ pub const PackageManager = struct { 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.loadNpmrcFromFile( + ctx.allocator, + ctx.install orelse brk: { + const install_ = ctx.allocator.create(Api.BunInstall) catch bun.outOfMemory(); + install_.* = std.mem.zeroes(Api.BunInstall); + ctx.install = install_; + break :brk install_; + }, + env, + true, + Path.joinAbsStringBufZ( + data_dir, + &buf, + &parts, + .auto, + ), + ); + } + bun.ini.loadNpmrcFromFile( ctx.allocator, ctx.install orelse brk: { @@ -8502,6 +8527,7 @@ pub const PackageManager = struct { }, env, true, + ".npmrc", ); var cpu_count = @as(u32, @truncate(((try std.Thread.getCpuCount()) + 1))); diff --git a/test/cli/install/registry/bun-install-registry.test.ts b/test/cli/install/registry/bun-install-registry.test.ts index e33965d1a6e1a..75254b29c5c68 100644 --- a/test/cli/install/registry/bun-install-registry.test.ts +++ b/test/cli/install/registry/bun-install-registry.test.ts @@ -189,6 +189,97 @@ registry = http://localhost:${port}/ await Bun.$`${bunExe()} install`.cwd(packageDir).throws(true); }); + it("works with home config", async () => { + console.log("package dir", packageDir); + await Bun.$`rm -rf ${packageDir}/bunfig.toml`; + + const homeDir = `${packageDir}/home_dir`; + await Bun.$`mkdir -p ${homeDir}`; + console.log("home dir", homeDir); + + const ini = /* ini */ ` + registry=http://localhost:${port}/ + `; + + await Bun.$`echo ${ini} > ${homeDir}/.npmrc`; + await Bun.$`echo ${JSON.stringify({ + name: "foo", + dependencies: { + "no-deps": "1.0.0", + }, + })} > package.json`.cwd(packageDir); + await Bun.$`${bunExe()} install` + .env({ + ...process.env, + XDG_CONFIG_HOME: `${homeDir}`, + }) + .cwd(packageDir) + .throws(true); + }); + + it("works with two configs", async () => { + await Bun.$`rm -rf ${packageDir}/bunfig.toml`; + + console.log("package dir", packageDir); + const packageIni = /* ini */ ` + @types:registry=http://localhost:${port}/ + `; + await Bun.$`echo ${packageIni} > ${packageDir}/.npmrc`; + + const homeDir = `${packageDir}/home_dir`; + await Bun.$`mkdir -p ${homeDir}`; + console.log("home dir", homeDir); + const homeIni = /* ini */ ` + registry = http://localhost:${port}/ + `; + await Bun.$`echo ${homeIni} > ${homeDir}/.npmrc`; + + await Bun.$`echo ${JSON.stringify({ + name: "foo", + dependencies: { + "no-deps": "1.0.0", + "@types/no-deps": "1.0.0", + }, + })} > package.json`.cwd(packageDir); + await Bun.$`${bunExe()} install` + .env({ + ...process.env, + XDG_CONFIG_HOME: `${homeDir}`, + }) + .cwd(packageDir) + .throws(true); + }); + + it("package config overrides home config", async () => { + await Bun.$`rm -rf ${packageDir}/bunfig.toml`; + + console.log("package dir", packageDir); + const packageIni = /* ini */ ` + @types:registry=http://localhost:${port}/ + `; + await Bun.$`echo ${packageIni} > ${packageDir}/.npmrc`; + + const homeDir = `${packageDir}/home_dir`; + await Bun.$`mkdir -p ${homeDir}`; + console.log("home dir", homeDir); + const homeIni = /* ini */ "@types:registry=https://registry.npmjs.org/"; + await Bun.$`echo ${homeIni} > ${homeDir}/.npmrc`; + + await Bun.$`echo ${JSON.stringify({ + name: "foo", + dependencies: { + "@types/no-deps": "1.0.0", + }, + })} > package.json`.cwd(packageDir); + await Bun.$`${bunExe()} install` + .env({ + ...process.env, + XDG_CONFIG_HOME: `${homeDir}`, + }) + .cwd(packageDir) + .throws(true); + }); + it("default registry from env variable", async () => { const ini = /* ini */ ` registry=\${LOL}