Skip to content

Commit

Permalink
.npmrc follow up (#12390)
Browse files Browse the repository at this point in the history
Co-authored-by: Jarred Sumner <[email protected]>
Co-authored-by: Dylan Conway <[email protected]>
  • Loading branch information
3 people authored Jul 11, 2024
1 parent e866793 commit cdc68a2
Show file tree
Hide file tree
Showing 8 changed files with 301 additions and 126 deletions.
10 changes: 8 additions & 2 deletions docs/install/npmrc.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ Bun supports loading configuration options from [`.npmrc`](https://docs.npmjs.co

{% callout %}

**NOTE**: We recommend migrating your `.npmrc` file to Bun's [`bunfig.toml`](/docs/runtime/bunfig) format, as it provides more flexible options and can let you configure Bun-specific configuration options.
**NOTE**: We recommend migrating your `.npmrc` file to Bun's [`bunfig.toml`](/docs/runtime/bunfig) format, as it provides more flexible options and can let you configure Bun-specific options.

{% /callout %}

Expand Down Expand Up @@ -50,16 +50,22 @@ Allows you to set options for a specific registry:


# or you could set a username and password
# note that the password is base64 encoded
//http://localhost:4873/:username=myusername

//http://localhost:4873/:_password=${NPM_PASSWORD}

# or use _auth, which is your username and password
# combined into a single string, which is then base 64 encoded
//http://localhost:4873/:_auth=${NPM_AUTH}
```

The following options are supported:

- `_authToken`
- `username`
- `_password`
- `_password` (base64 encoded password)
- `_auth` (base64 encoded username:password, e.g. `btoa(username + ":" + password)`)

The equivalent `bunfig.toml` option is to add a key in [`install.scopes`](/docs/runtime/bunfig#install-registry):

Expand Down
1 change: 0 additions & 1 deletion src/bun.js/bindings/JSPropertyIterator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ extern "C" JSPropertyIterator* Bun__JSPropertyIterator__create(JSC::JSGlobalObje
JSC::VM& vm = globalObject->vm();
JSC::JSValue value = JSValue::decode(encodedValue);
JSC::JSObject* object = value.getObject();
ASSERT(object != NULL);

auto scope = DECLARE_THROW_SCOPE(vm);
JSC::PropertyNameArray array(vm, PropertyNameMode::StringsAndSymbols, PrivateSymbolMode::Exclude);
Expand Down
162 changes: 123 additions & 39 deletions src/ini.zig
Original file line number Diff line number Diff line change
Expand Up @@ -705,16 +705,18 @@ pub const ConfigIterator = struct {
registry_url: []const u8,
optname: Opt,
value: []const u8,
loc: Loc,

pub const Opt = enum {
/// base64 authentication string
/// `${username}:${password}` encoded in base64
_auth,

/// authentication string
_authToken,

username,

/// this is encoded as base64 in .npmrc
_password,

email,
Expand All @@ -724,8 +726,42 @@ pub const ConfigIterator = struct {

/// path to key file
keyfile,

pub fn isBase64Encoded(this: Opt) bool {
return switch (this) {
._auth, ._password => true,
else => false,
};
}
};

/// Duplicate the value, decoding it if it is base64 encoded.
pub fn dupeValueDecoded(
this: *const Item,
allocator: Allocator,
log: *bun.logger.Log,
source: *const bun.logger.Source,
) ?[]const u8 {
if (this.optname.isBase64Encoded()) {
if (this.value.len == 0) return "";
const len = bun.base64.decodeLen(this.value);
var slice = allocator.alloc(u8, len) catch bun.outOfMemory();
const result = bun.base64.decode(slice[0..], this.value);
if (result.status != .success) {
log.addErrorFmt(
source,
this.loc,
allocator,
"{s} is not valid base64",
.{@tagName(this.optname)},
) catch bun.outOfMemory();
return null;
}
return allocator.dupe(u8, slice[0..result.count]) catch bun.outOfMemory();
}
return allocator.dupe(u8, this.value) catch bun.outOfMemory();
}

pub fn format(this: *const @This(), comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
try writer.print("//{s}:{s}={s}", .{ this.registry_url, @tagName(this.optname), this.value });
}
Expand Down Expand Up @@ -767,6 +803,7 @@ pub const ConfigIterator = struct {
.registry_url = url_part,
.value = value,
.optname = std.meta.stringToEnum(Item.Opt, name).?,
.loc = prop.key.?.loc,
},
};
}
Expand Down Expand Up @@ -828,8 +865,6 @@ pub const ScopeIterator = struct {
},
};
}

return .none;
}
}
}
Expand All @@ -843,8 +878,9 @@ pub fn loadNpmrcFromFile(
install: *bun.Schema.Api.BunInstall,
env: *bun.DotEnv.Loader,
auto_loaded: bool,
log: *bun.logger.Log,
) !void {
) 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)) {
.result => |fd| fd,
.err => |err| {
Expand All @@ -858,22 +894,25 @@ pub fn loadNpmrcFromFile(
};
defer _ = bun.sys.close(npmrc_file);

var npmrc_contents = std.ArrayList(u8).init(allocator);
defer npmrc_contents.deinit();
switch (bun.sys.File.readToEndWithArrayList(bun.sys.File{ .handle = npmrc_file }, &npmrc_contents)) {
.result => {},
.err => |err| {
// TODO: should this exit(1)?
const source = switch (bun.sys.File.toSource(".npmrc", allocator)) {
.result => |s| s,
.err => |e| {
Output.prettyErrorln("{}\nwhile reading .npmrc \"{s}\"", .{
err,
e,
".npmrc",
});
Global.exit(1);
},
}
const source = bun.logger.Source.initPathString(".npmrc", npmrc_contents.items[0..]);
};
defer allocator.free(source.contents);

return loadNpmrc(allocator, install, env, auto_loaded, log, &source);
loadNpmrc(allocator, install, env, auto_loaded, &log, &source) catch {
if (log.errors == 1)
Output.warn("Encountered an error while reading <b>.npmrc<r>:\n", .{})
else
Output.warn("Encountered errors while reading <b>.npmrc<r>:\n", .{});
};
log.printForLogLevel(Output.errorWriter()) catch bun.outOfMemory();
}

pub fn loadNpmrc(
Expand Down Expand Up @@ -1065,7 +1104,7 @@ pub fn loadNpmrc(
// - @myorg:registry=https://somewhere-else.com/myorg
const conf_item: bun.ini.ConfigIterator.Item = conf_item_;
switch (conf_item.optname) {
._auth, .email, .certfile, .keyfile => {
.email, .certfile, .keyfile => {
log.addWarningFmt(
source,
iter.config.properties.at(iter.prop_idx - 1).key.?.loc,
Expand Down Expand Up @@ -1095,10 +1134,19 @@ pub fn loadNpmrc(
};

switch (conf_item.optname) {
._authToken => v.token = allocator.dupe(u8, conf_item.value) catch bun.outOfMemory(),
.username => v.username = allocator.dupe(u8, conf_item.value) catch bun.outOfMemory(),
._password => v.password = allocator.dupe(u8, conf_item.value) catch bun.outOfMemory(),
._auth, .email, .certfile, .keyfile => unreachable,
._authToken => {
if (conf_item.dupeValueDecoded(allocator, log, source)) |x| v.token = x;
},
.username => {
if (conf_item.dupeValueDecoded(allocator, log, source)) |x| v.username = x;
},
._password => {
if (conf_item.dupeValueDecoded(allocator, log, source)) |x| v.password = x;
},
._auth => {
_ = @"handle _auth"(allocator, v, &conf_item, log, source);
},
.email, .certfile, .keyfile => unreachable,
}
continue;
}
Expand All @@ -1115,10 +1163,19 @@ pub fn loadNpmrc(
}
matched_at_least_one = true;
switch (conf_item.optname) {
._authToken => v.token = allocator.dupe(u8, conf_item.value) catch bun.outOfMemory(),
.username => v.username = allocator.dupe(u8, conf_item.value) catch bun.outOfMemory(),
._password => v.password = allocator.dupe(u8, conf_item.value) catch bun.outOfMemory(),
._auth, .email, .certfile, .keyfile => unreachable,
._authToken => {
if (conf_item.dupeValueDecoded(allocator, log, source)) |x| v.token = x;
},
.username => {
if (conf_item.dupeValueDecoded(allocator, log, source)) |x| v.username = x;
},
._password => {
if (conf_item.dupeValueDecoded(allocator, log, source)) |x| v.password = x;
},
._auth => {
_ = @"handle _auth"(allocator, v, &conf_item, log, source);
},
.email, .certfile, .keyfile => unreachable,
}
// We have to keep going as it could match multiple scopes
continue;
Expand All @@ -1142,23 +1199,50 @@ pub fn loadNpmrc(
}

const had_errors = log.hasErrors();
log.printForLogLevel(Output.errorWriter()) catch bun.outOfMemory();
if (had_errors) {
return error.ParserError;
}
}

// if (!bun.Environment.isDebug) {
// if (!@import("./bun.js/module_loader.zig").ModuleLoader.is_allowed_to_use_internal_testing_apis)
// return;
// }

// if (bun.getenvTruthy("BUN_TEST_LOG_DEFAULT_REGISTRY")) {
// if (install.default_registry) |reg| {
// Output.print("Default registry url: {s}\n", .{reg.url});
// Output.print("Default registry token: {s}\n", .{reg.token});
// Output.print("Default registry username: {s}\n", .{reg.username});
// Output.print("Default registry password: {s}\n", .{reg.password});
// Output.flush();
// }
// }
fn @"handle _auth"(
allocator: Allocator,
v: *bun.Schema.Api.NpmRegistry,
conf_item: *const ConfigIterator.Item,
log: *bun.logger.Log,
source: *const bun.logger.Source,
) void {
if (conf_item.value.len == 0) {
log.addErrorFmt(
source,
conf_item.loc,
allocator,
"invalid _auth value, expected it to be \"\\<username\\>:\\<password\\>\" encoded in base64, but got an empty string",
.{},
) catch bun.outOfMemory();
return;
}
const decode_len = bun.base64.decodeLen(conf_item.value);
const decoded = allocator.alloc(u8, decode_len) catch bun.outOfMemory();
const result = bun.base64.decode(decoded[0..], conf_item.value);
if (!result.isSuccessful()) {
defer allocator.free(decoded);
log.addErrorFmt(source, conf_item.loc, allocator, "invalid base64", .{}) catch bun.outOfMemory();
return;
}
const @"username:password" = decoded[0..result.count];
const colon_idx = std.mem.indexOfScalar(u8, @"username:password", ':') orelse {
defer allocator.free(decoded);
log.addErrorFmt(source, conf_item.loc, allocator, "invalid _auth value, expected it to be \"\\<username\\>:\\<password\\>\" encoded in base 64, but got:\n\n{s}", .{decoded}) catch bun.outOfMemory();
return;
};
const username = @"username:password"[0..colon_idx];
if (colon_idx + 1 >= @"username:password".len) {
defer allocator.free(decoded);
log.addErrorFmt(source, conf_item.loc, allocator, "invalid _auth value, expected it to be \"\\<username\\>:\\<password\\>\" encoded in base64, but got:\n\n{s}", .{decoded}) catch bun.outOfMemory();
return;
}
const password = @"username:password"[colon_idx + 1 ..];
v.username = username;
v.password = password;
return;
}
22 changes: 11 additions & 11 deletions src/install/install.zig
Original file line number Diff line number Diff line change
Expand Up @@ -8457,18 +8457,18 @@ pub const PackageManager = struct {
env.loadProcess();
try env.load(entries_option.entries, &[_][]u8{}, .production, false);

var log = logger.Log.init(ctx.allocator);
defer log.deinit();
initializeStore();
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, &log) catch {
if (log.errors == 1) Output.warn("Encountered an error while reading <b>.npmrc<r>:", .{}) else Output.warn("Encountered errors while reading <b>.npmrc<r>:\n", .{});
log.printForLogLevel(Output.errorWriter()) catch bun.outOfMemory();
};
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,
);

var cpu_count = @as(u32, @truncate(((try std.Thread.getCpuCount()) + 1)));

Expand Down
Loading

0 comments on commit cdc68a2

Please sign in to comment.