From 84f15e9a3c52569feac55c2fc0dd01c9b988ecb5 Mon Sep 17 00:00:00 2001 From: Dylan Conway Date: Mon, 7 Oct 2024 20:06:10 -0700 Subject: [PATCH 01/19] certificates --- src/bun.js/api/bun/socket.zig | 10 ++++-- src/bun.js/api/server.zig | 58 ++++++++++++++++----------------- src/bun.js/webcore/response.zig | 2 +- src/bun.zig | 42 +++++++++++++++++++++++- src/bun_js.zig | 2 +- src/cli/create_command.zig | 2 +- src/cli/test_command.zig | 2 +- src/cli/upgrade_command.zig | 4 +-- src/compile_target.zig | 2 +- src/http.zig | 48 +++++++++++++++++++++------ src/install/install.zig | 52 +++++++++++++++++++++++++++-- src/napi/napi.zig | 2 +- src/resolver/resolver.zig | 2 +- 13 files changed, 173 insertions(+), 55 deletions(-) diff --git a/src/bun.js/api/bun/socket.zig b/src/bun.js/api/bun/socket.zig index 17220320670b1..71b2061df6a69 100644 --- a/src/bun.js/api/bun/socket.zig +++ b/src/bun.js/api/bun/socket.zig @@ -642,7 +642,10 @@ pub const Listener = struct { } } } - const ctx_opts: uws.us_bun_socket_context_options_t = JSC.API.ServerConfig.SSLConfig.asUSockets(ssl); + const ctx_opts: uws.us_bun_socket_context_options_t = if (ssl != null) + JSC.API.ServerConfig.SSLConfig.asUSockets(ssl.?) + else + .{}; vm.eventLoop().ensureWaker(); @@ -1172,7 +1175,10 @@ pub const Listener = struct { } } - const ctx_opts: uws.us_bun_socket_context_options_t = JSC.API.ServerConfig.SSLConfig.asUSockets(socket_config.ssl); + const ctx_opts: uws.us_bun_socket_context_options_t = if (ssl != null) + JSC.API.ServerConfig.SSLConfig.asUSockets(ssl.?) + else + .{}; const socket_context = uws.us_create_bun_socket_context(@intFromBool(ssl_enabled), uws.Loop.get(), @sizeOf(usize), ctx_opts) orelse { const err = JSC.SystemError{ diff --git a/src/bun.js/api/server.zig b/src/bun.js/api/server.zig index be5e09ad249e9..ff9b5e72fba94 100644 --- a/src/bun.js/api/server.zig +++ b/src/bun.js/api/server.zig @@ -583,41 +583,39 @@ pub const ServerConfig = struct { const log = Output.scoped(.SSLConfig, false); - pub fn asUSockets(this_: ?SSLConfig) uws.us_bun_socket_context_options_t { + pub fn asUSockets(this: SSLConfig) uws.us_bun_socket_context_options_t { var ctx_opts: uws.us_bun_socket_context_options_t = .{}; - if (this_) |ssl_config| { - if (ssl_config.key_file_name != null) - ctx_opts.key_file_name = ssl_config.key_file_name; - if (ssl_config.cert_file_name != null) - ctx_opts.cert_file_name = ssl_config.cert_file_name; - if (ssl_config.ca_file_name != null) - ctx_opts.ca_file_name = ssl_config.ca_file_name; - if (ssl_config.dh_params_file_name != null) - ctx_opts.dh_params_file_name = ssl_config.dh_params_file_name; - if (ssl_config.passphrase != null) - ctx_opts.passphrase = ssl_config.passphrase; - ctx_opts.ssl_prefer_low_memory_usage = @intFromBool(ssl_config.low_memory_mode); + if (this.key_file_name != null) + ctx_opts.key_file_name = this.key_file_name; + if (this.cert_file_name != null) + ctx_opts.cert_file_name = this.cert_file_name; + if (this.ca_file_name != null) + ctx_opts.ca_file_name = this.ca_file_name; + if (this.dh_params_file_name != null) + ctx_opts.dh_params_file_name = this.dh_params_file_name; + if (this.passphrase != null) + ctx_opts.passphrase = this.passphrase; + ctx_opts.ssl_prefer_low_memory_usage = @intFromBool(this.low_memory_mode); - if (ssl_config.key) |key| { - ctx_opts.key = key.ptr; - ctx_opts.key_count = ssl_config.key_count; - } - if (ssl_config.cert) |cert| { - ctx_opts.cert = cert.ptr; - ctx_opts.cert_count = ssl_config.cert_count; - } - if (ssl_config.ca) |ca| { - ctx_opts.ca = ca.ptr; - ctx_opts.ca_count = ssl_config.ca_count; - } + if (this.key) |key| { + ctx_opts.key = key.ptr; + ctx_opts.key_count = this.key_count; + } + if (this.cert) |cert| { + ctx_opts.cert = cert.ptr; + ctx_opts.cert_count = this.cert_count; + } + if (this.ca) |ca| { + ctx_opts.ca = ca.ptr; + ctx_opts.ca_count = this.ca_count; + } - if (ssl_config.ssl_ciphers != null) { - ctx_opts.ssl_ciphers = ssl_config.ssl_ciphers; - } - ctx_opts.request_cert = ssl_config.request_cert; - ctx_opts.reject_unauthorized = ssl_config.reject_unauthorized; + if (this.ssl_ciphers != null) { + ctx_opts.ssl_ciphers = this.ssl_ciphers; } + ctx_opts.request_cert = this.request_cert; + ctx_opts.reject_unauthorized = this.reject_unauthorized; return ctx_opts; } diff --git a/src/bun.js/webcore/response.zig b/src/bun.js/webcore/response.zig index 76d7d07aaa5e7..184c1f9cbbc06 100644 --- a/src/bun.js/webcore/response.zig +++ b/src/bun.js/webcore/response.zig @@ -1797,7 +1797,7 @@ pub const Fetch = struct { fetch_options: FetchOptions, promise: JSC.JSPromise.Strong, ) !*FetchTasklet { - http.HTTPThread.init(); + http.HTTPThread.init(&.{}); var node = try get( allocator, global, diff --git a/src/bun.zig b/src/bun.zig index 243ad4167361b..2b05f0344fe4c 100644 --- a/src/bun.zig +++ b/src/bun.zig @@ -718,7 +718,7 @@ pub const Analytics = @import("./analytics/analytics_thread.zig"); pub usingnamespace @import("./tagged_pointer.zig"); -pub fn once(comptime function: anytype, comptime ReturnType: type) ReturnType { +pub fn onceUnsafe(comptime function: anytype, comptime ReturnType: type) ReturnType { const Result = struct { var value: ReturnType = undefined; var ran = false; @@ -3928,3 +3928,43 @@ pub fn indexOfPointerInSlice(comptime T: type, slice: []const T, item: *const T) const index = @divExact(offset, @sizeOf(T)); return index; } + +/// Copied from zig std. Modified to accept arguments. +pub fn once(comptime f: anytype) Once(f) { + return Once(f){}; +} + +/// Copied from zig std. Modified to accept arguments. +/// +/// An object that executes the function `f` just once. +/// It is undefined behavior if `f` re-enters the same Once instance. +pub fn Once(comptime f: anytype) type { + return struct { + done: bool = false, + mutex: std.Thread.Mutex = std.Thread.Mutex{}, + + /// Call the function `f`. + /// If `call` is invoked multiple times `f` will be executed only the + /// first time. + /// The invocations are thread-safe. + pub fn call(self: *@This(), args: std.meta.ArgsTuple(@TypeOf(f))) void { + if (@atomicLoad(bool, &self.done, .acquire)) + return; + + return self.callSlow(args); + } + + fn callSlow(self: *@This(), args: std.meta.ArgsTuple(@TypeOf(f))) void { + @setCold(true); + + self.mutex.lock(); + defer self.mutex.unlock(); + + // The first thread to acquire the mutex gets to run the initializer + if (!self.done) { + @call(.auto, f, args); + @atomicStore(bool, &self.done, true, .release); + } + } + }; +} diff --git a/src/bun_js.zig b/src/bun_js.zig index e5eff889cef6f..bb8b1e8c48f7f 100644 --- a/src/bun_js.zig +++ b/src/bun_js.zig @@ -127,7 +127,7 @@ pub const Run = struct { fn doPreconnect(preconnect: []const string) void { if (preconnect.len == 0) return; - bun.HTTPThread.init(); + bun.HTTPThread.init(&.{}); for (preconnect) |url_str| { const url = bun.URL.parse(url_str); diff --git a/src/cli/create_command.zig b/src/cli/create_command.zig index 16ac76623ecc3..2d6577a4be0d2 100644 --- a/src/cli/create_command.zig +++ b/src/cli/create_command.zig @@ -241,7 +241,7 @@ pub const CreateCommand = struct { @setCold(true); Global.configureAllocator(.{ .long_running = false }); - HTTP.HTTPThread.init(); + HTTP.HTTPThread.init(&.{}); var create_options = try CreateOptions.parse(ctx); const positionals = create_options.positionals; diff --git a/src/cli/test_command.zig b/src/cli/test_command.zig index b3b2604d77f78..b0f1000b5bd45 100644 --- a/src/cli/test_command.zig +++ b/src/cli/test_command.zig @@ -741,7 +741,7 @@ pub const TestCommand = struct { break :brk loader; }; bun.JSC.initialize(false); - HTTPThread.init(); + HTTPThread.init(&.{}); var snapshot_file_buf = std.ArrayList(u8).init(ctx.allocator); var snapshot_values = Snapshots.ValuesHashMap.init(ctx.allocator); diff --git a/src/cli/upgrade_command.zig b/src/cli/upgrade_command.zig index c75452a0fdfd4..b89d1777addb0 100644 --- a/src/cli/upgrade_command.zig +++ b/src/cli/upgrade_command.zig @@ -133,7 +133,7 @@ pub const UpgradeCheckerThread = struct { std.time.sleep(std.time.ns_per_ms * delay); Output.Source.configureThread(); - HTTP.HTTPThread.init(); + HTTP.HTTPThread.init(&.{}); defer { js_ast.Expr.Data.Store.deinit(); @@ -440,7 +440,7 @@ pub const UpgradeCommand = struct { } fn _exec(ctx: Command.Context) !void { - HTTP.HTTPThread.init(); + HTTP.HTTPThread.init(&.{}); var filesystem = try fs.FileSystem.init(null); var env_loader: DotEnv.Loader = brk: { diff --git a/src/compile_target.zig b/src/compile_target.zig index a6ec5f076c7ae..bd060d24bb281 100644 --- a/src/compile_target.zig +++ b/src/compile_target.zig @@ -137,7 +137,7 @@ const HTTP = bun.http; const MutableString = bun.MutableString; const Global = bun.Global; pub fn downloadToPath(this: *const CompileTarget, env: *bun.DotEnv.Loader, allocator: std.mem.Allocator, dest_z: [:0]const u8) !void { - HTTP.HTTPThread.init(); + HTTP.HTTPThread.init(&.{}); var refresher = bun.Progress{}; { diff --git a/src/http.zig b/src/http.zig index c2c0da7ba577b..941d6c1abac18 100644 --- a/src/http.zig +++ b/src/http.zig @@ -587,12 +587,19 @@ fn NewHTTPContext(comptime ssl: bool) type { pub fn initWithClientConfig(this: *@This(), client: *HTTPClient) !void { if (!comptime ssl) { - unreachable; + @compileError("ssl only"); } var opts = client.tls_props.?.asUSockets(); opts.request_cert = 1; opts.reject_unauthorized = 0; - const socket = uws.us_create_bun_socket_context(ssl_int, http_thread.loop.loop, @sizeOf(usize), opts); + try this.initWithOpts(&opts); + } + + pub fn initWithOpts(this: *@This(), opts: *const uws.us_bun_socket_context_options_t) !void { + if (!comptime ssl) { + @compileError("ssl only"); + } + const socket = uws.us_create_bun_socket_context(ssl_int, http_thread.loop.loop, @sizeOf(usize), opts.*); if (socket == null) { return error.FailedToOpenSocket; } @@ -607,6 +614,22 @@ fn NewHTTPContext(comptime ssl: bool) type { ); } + pub fn initWithThreadOpts(this: *@This(), init_opts: *const HTTPThread.InitOpts) !void { + if (!comptime ssl) { + @compileError("ssl only"); + } + var opts: uws.us_bun_socket_context_options_t = .{ + .ca = if (init_opts.ca.len > 0) @ptrCast(init_opts.ca) else null, + .ca_count = @intCast(init_opts.ca.len), + .ca_file_name = if (init_opts.abs_ca_file_name.len > 0) init_opts.abs_ca_file_name else null, + + // TODO: is this needed? + .request_cert = 1, + }; + + try this.initWithOpts(&opts); + } + pub fn init(this: *@This()) !void { if (comptime ssl) { const opts: uws.us_bun_socket_context_options_t = .{ @@ -1005,7 +1028,12 @@ pub const HTTPThread = struct { return this.lazy_libdeflater.?; } - fn initOnce() void { + pub const InitOpts = struct { + ca: []stringZ = &.{}, + abs_ca_file_name: stringZ = &.{}, + }; + + fn initOnce(opts: *const InitOpts) void { http_thread = .{ .loop = undefined, .http_context = .{ @@ -1022,17 +1050,17 @@ pub const HTTPThread = struct { .stack_size = bun.default_thread_stack_size, }, onStart, - .{}, + .{opts}, ) catch |err| Output.panic("Failed to start HTTP Client thread: {s}", .{@errorName(err)}); thread.detach(); } - var init_once = std.once(initOnce); + var init_once = bun.once(initOnce); - pub fn init() void { - init_once.call(); + pub fn init(opts: *const InitOpts) void { + init_once.call(.{opts}); } - pub fn onStart() void { + pub fn onStart(opts: *const InitOpts) void { Output.Source.configureNamedThread("HTTP Client"); default_arena = Arena.init() catch unreachable; default_allocator = default_arena.allocator(); @@ -1047,7 +1075,7 @@ pub const HTTPThread = struct { http_thread.loop = loop; http_thread.http_context.init() catch @panic("Failed to init http context"); - http_thread.https_context.init() catch @panic("Failed to init https context"); + http_thread.https_context.initWithThreadOpts(opts) catch @panic("Failed to init https context"); http_thread.has_awoken.store(true, .monotonic); http_thread.processEvents(); } @@ -2479,7 +2507,7 @@ pub const AsyncHTTP = struct { } pub fn sendSync(this: *AsyncHTTP) anyerror!picohttp.Response { - HTTPThread.init(); + HTTPThread.init(&.{}); var ctx = try bun.default_allocator.create(SingleHTTPChannel); ctx.* = SingleHTTPChannel.init(); diff --git a/src/install/install.zig b/src/install/install.zig index 95e6cb674801a..3effa453ce683 100644 --- a/src/install/install.zig +++ b/src/install/install.zig @@ -6943,6 +6943,9 @@ pub const PackageManager = struct { publish_config: PublishConfig = .{}, + ca: []const string = &.{}, + ca_file_name: string = &.{}, + pub const PublishConfig = struct { access: ?Access = null, tag: string = "", @@ -7392,6 +7395,13 @@ pub const PackageManager = struct { if (cli.publish_config.auth_type) |auth_type| { this.publish_config.auth_type = auth_type; } + + if (cli.ca.len > 0) { + this.ca = cli.ca; + } + if (cli.ca_file_name.len > 0) { + this.ca_file_name = cli.ca_file_name; + } } else { this.log_level = if (default_disable_progress_bar) LogLevel.default_no_progress else LogLevel.default; PackageManager.verbose_install = false; @@ -8334,9 +8344,6 @@ pub const PackageManager = struct { cli: CommandLineArguments, subcommand: Subcommand, ) !struct { *PackageManager, string } { - // assume that spawning a thread will take a lil so we do that asap - HTTP.HTTPThread.init(); - if (cli.global) { var explicit_global_dir: string = ""; if (ctx.install) |opts| { @@ -8677,6 +8684,35 @@ pub const PackageManager = struct { subcommand, ); + var ca: []stringZ = &.{}; + if (manager.options.ca.len > 0) { + ca = try manager.allocator.alloc(stringZ, manager.options.ca.len); + for (ca, manager.options.ca) |*z, s| { + z.* = try manager.allocator.dupeZ(u8, s); + } + } + + var abs_ca_file_name: stringZ = &.{}; + if (manager.options.ca_file_name.len > 0) { + // resolve with original cwd + if (std.fs.path.isAbsolute(manager.options.ca_file_name)) { + abs_ca_file_name = try manager.allocator.dupeZ(u8, manager.options.ca_file_name); + } else { + var path_buf: bun.PathBuffer = undefined; + abs_ca_file_name = try manager.allocator.dupeZ(u8, bun.path.joinAbsStringBuf( + original_cwd_clone, + &path_buf, + &.{manager.options.ca_file_name}, + .auto, + )); + } + } + + HTTP.HTTPThread.init(&.{ + .ca = ca, + .abs_ca_file_name = abs_ca_file_name, + }); + manager.timestamp_for_manifest_cache_control = brk: { if (comptime bun.Environment.allow_assert) { if (env.get("BUN_CONFIG_MANIFEST_CACHE_CONTROL_TIMESTAMP")) |cache_control| { @@ -9207,6 +9243,8 @@ pub const PackageManager = struct { clap.parseParam("-p, --production Don't install devDependencies") catch unreachable, clap.parseParam("--no-save Don't update package.json or save a lockfile") catch unreachable, clap.parseParam("--save Save to package.json (true by default)") catch unreachable, + clap.parseParam("--ca ... Provide a Certificate Authority signing certificate") catch unreachable, + clap.parseParam("--cafile The same as `--ca`, but is a file path to the certificate") catch unreachable, clap.parseParam("--dry-run Don't install anything") catch unreachable, clap.parseParam("--frozen-lockfile Disallow changes to lockfile") catch unreachable, clap.parseParam("-f, --force Always request the latest versions from the registry & reinstall all dependencies") catch unreachable, @@ -9349,6 +9387,9 @@ pub const PackageManager = struct { publish_config: Options.PublishConfig = .{}, + ca: []const string = &.{}, + ca_file_name: string = "", + const PatchOpts = union(enum) { nothing: struct {}, patch: struct {}, @@ -9688,6 +9729,11 @@ pub const PackageManager = struct { cli.ignore_scripts = args.flag("--ignore-scripts"); cli.trusted = args.flag("--trust"); cli.no_summary = args.flag("--no-summary"); + cli.ca = args.options("--ca"); + + if (args.option("--cafile")) |ca_file_name| { + cli.ca_file_name = ca_file_name; + } // commands that support --filter if (comptime subcommand.supportsWorkspaceFiltering()) { diff --git a/src/napi/napi.zig b/src/napi/napi.zig index baa675eb31998..39145743e314a 100644 --- a/src/napi/napi.zig +++ b/src/napi/napi.zig @@ -822,7 +822,7 @@ pub export fn napi_make_callback(env: napi_env, _: *anyopaque, recv_: napi_value // We don't want to fail to load the library because of that // so we instead return an error and warn the user fn notImplementedYet(comptime name: []const u8) void { - bun.once( + bun.onceUnsafe( struct { pub fn warn() void { if (JSC.VirtualMachine.get().log.level.atLeast(.warn)) { diff --git a/src/resolver/resolver.zig b/src/resolver/resolver.zig index 725a6ea480188..be558fb331810 100644 --- a/src/resolver/resolver.zig +++ b/src/resolver/resolver.zig @@ -563,7 +563,7 @@ pub const Resolver = struct { pub fn getPackageManager(this: *Resolver) *PackageManager { return this.package_manager orelse brk: { - bun.HTTPThread.init(); + bun.HTTPThread.init(&.{}); const pm = PackageManager.initWithRuntime( this.log, this.opts.install, From 7a497d3b156c80173bc0a5710c41386332066957 Mon Sep 17 00:00:00 2001 From: Dylan Conway Date: Tue, 8 Oct 2024 00:25:00 -0700 Subject: [PATCH 02/19] fix uaf --- src/http.zig | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/http.zig b/src/http.zig index 941d6c1abac18..8c132ef505d1c 100644 --- a/src/http.zig +++ b/src/http.zig @@ -1050,7 +1050,7 @@ pub const HTTPThread = struct { .stack_size = bun.default_thread_stack_size, }, onStart, - .{opts}, + .{opts.*}, ) catch |err| Output.panic("Failed to start HTTP Client thread: {s}", .{@errorName(err)}); thread.detach(); } @@ -1060,7 +1060,7 @@ pub const HTTPThread = struct { init_once.call(.{opts}); } - pub fn onStart(opts: *const InitOpts) void { + pub fn onStart(opts: InitOpts) void { Output.Source.configureNamedThread("HTTP Client"); default_arena = Arena.init() catch unreachable; default_allocator = default_arena.allocator(); @@ -1075,7 +1075,7 @@ pub const HTTPThread = struct { http_thread.loop = loop; http_thread.http_context.init() catch @panic("Failed to init http context"); - http_thread.https_context.initWithThreadOpts(opts) catch @panic("Failed to init https context"); + http_thread.https_context.initWithThreadOpts(&opts) catch @panic("Failed to init https context"); http_thread.has_awoken.store(true, .monotonic); http_thread.processEvents(); } From 403ef2e57ea1e0e6589fa90f191dd30d35833446 Mon Sep 17 00:00:00 2001 From: Dylan Conway Date: Tue, 8 Oct 2024 13:19:34 -0700 Subject: [PATCH 03/19] bubble CA errors --- packages/bun-usockets/src/context.c | 4 +- packages/bun-usockets/src/crypto/openssl.c | 19 ++++--- packages/bun-usockets/src/internal/internal.h | 3 +- packages/bun-usockets/src/libusockets.h | 10 +++- packages/bun-uws/src/HttpContext.h | 3 +- src/bun.js/api/bun/socket.zig | 5 +- .../bindings/ScriptExecutionContext.cpp | 3 +- src/cli/init_command.zig | 17 +++--- src/deps/uws.zig | 9 +++- src/http.zig | 53 +++++++++++++++---- src/sys.zig | 10 ++++ 11 files changed, 104 insertions(+), 32 deletions(-) diff --git a/packages/bun-usockets/src/context.c b/packages/bun-usockets/src/context.c index a59c80e83a0c5..664f7dabdd477 100644 --- a/packages/bun-usockets/src/context.c +++ b/packages/bun-usockets/src/context.c @@ -278,11 +278,11 @@ struct us_socket_context_t *us_create_socket_context(int ssl, struct us_loop_t * return context; } -struct us_socket_context_t *us_create_bun_socket_context(int ssl, struct us_loop_t *loop, int context_ext_size, struct us_bun_socket_context_options_t options) { +struct us_socket_context_t *us_create_bun_socket_context(int ssl, struct us_loop_t *loop, int context_ext_size, struct us_bun_socket_context_options_t options, enum create_bun_socket_error_t *err) { #ifndef LIBUS_NO_SSL if (ssl) { /* This function will call us, again, with SSL = false and a bigger ext_size */ - return (struct us_socket_context_t *) us_internal_bun_create_ssl_socket_context(loop, context_ext_size, options); + return (struct us_socket_context_t *) us_internal_bun_create_ssl_socket_context(loop, context_ext_size, options, err); } #endif diff --git a/packages/bun-usockets/src/crypto/openssl.c b/packages/bun-usockets/src/crypto/openssl.c index 232d5f8ff9c16..2c0420109595b 100644 --- a/packages/bun-usockets/src/crypto/openssl.c +++ b/packages/bun-usockets/src/crypto/openssl.c @@ -1104,7 +1104,8 @@ int us_verify_callback(int preverify_ok, X509_STORE_CTX *ctx) { } SSL_CTX *create_ssl_context_from_bun_options( - struct us_bun_socket_context_options_t options) { + struct us_bun_socket_context_options_t options, + enum create_bun_socket_error_t *err) { /* Create the context */ SSL_CTX *ssl_context = SSL_CTX_new(TLS_method()); @@ -1174,6 +1175,7 @@ SSL_CTX *create_ssl_context_from_bun_options( STACK_OF(X509_NAME) * ca_list; ca_list = SSL_load_client_CA_file(options.ca_file_name); if (ca_list == NULL) { + *err = CREATE_BUN_SOCKET_ERROR_LOAD_CA_FILE; free_ssl_context(ssl_context); return NULL; } @@ -1181,6 +1183,7 @@ SSL_CTX *create_ssl_context_from_bun_options( SSL_CTX_set_client_CA_list(ssl_context, ca_list); if (SSL_CTX_load_verify_locations(ssl_context, options.ca_file_name, NULL) != 1) { + *err = CREATE_BUN_SOCKET_ERROR_INVALID_CA_FILE; free_ssl_context(ssl_context); return NULL; } @@ -1203,6 +1206,7 @@ SSL_CTX *create_ssl_context_from_bun_options( } if (!add_ca_cert_to_ctx_store(ssl_context, options.ca[i], cert_store)) { + *err = CREATE_BUN_SOCKET_ERROR_INVALID_CA; free_ssl_context(ssl_context); return NULL; } @@ -1338,7 +1342,8 @@ void us_bun_internal_ssl_socket_context_add_server_name( struct us_bun_socket_context_options_t options, void *user) { /* Try and construct an SSL_CTX from options */ - SSL_CTX *ssl_context = create_ssl_context_from_bun_options(options); + enum create_bun_socket_error_t err = CREATE_BUN_SOCKET_ERROR_NONE; + SSL_CTX *ssl_context = create_ssl_context_from_bun_options(options, &err); /* Attach the user data to this context */ if (1 != SSL_CTX_set_ex_data(ssl_context, 0, user)) { @@ -1468,14 +1473,15 @@ struct us_internal_ssl_socket_context_t *us_internal_create_ssl_socket_context( struct us_internal_ssl_socket_context_t * us_internal_bun_create_ssl_socket_context( struct us_loop_t *loop, int context_ext_size, - struct us_bun_socket_context_options_t options) { + struct us_bun_socket_context_options_t options, + enum create_bun_socket_error_t *err) { /* If we haven't initialized the loop data yet, do so . * This is needed because loop data holds shared OpenSSL data and * the function is also responsible for initializing OpenSSL */ us_internal_init_loop_ssl_data(loop); /* First of all we try and create the SSL context from options */ - SSL_CTX *ssl_context = create_ssl_context_from_bun_options(options); + SSL_CTX *ssl_context = create_ssl_context_from_bun_options(options, err); if (!ssl_context) { /* We simply fail early if we cannot even create the OpenSSL context */ return NULL; @@ -1487,7 +1493,7 @@ us_internal_bun_create_ssl_socket_context( (struct us_internal_ssl_socket_context_t *)us_create_bun_socket_context( 0, loop, sizeof(struct us_internal_ssl_socket_context_t) + context_ext_size, - options); + options, err); /* I guess this is the only optional callback */ context->on_server_name = NULL; @@ -1983,9 +1989,10 @@ struct us_internal_ssl_socket_t *us_internal_ssl_socket_wrap_with_tls( struct us_socket_context_t *old_context = us_socket_context(0, s); us_socket_context_ref(0,old_context); + enum create_bun_socket_error_t err = CREATE_BUN_SOCKET_ERROR_NONE; struct us_socket_context_t *context = us_create_bun_socket_context( 1, old_context->loop, sizeof(struct us_wrapped_socket_context_t), - options); + options, &err); // Handle SSL context creation failure if (UNLIKELY(!context)) { diff --git a/packages/bun-usockets/src/internal/internal.h b/packages/bun-usockets/src/internal/internal.h index 8c6c717504729..abc24a4e8300c 100644 --- a/packages/bun-usockets/src/internal/internal.h +++ b/packages/bun-usockets/src/internal/internal.h @@ -330,7 +330,8 @@ struct us_internal_ssl_socket_context_t *us_internal_create_ssl_socket_context( struct us_internal_ssl_socket_context_t * us_internal_bun_create_ssl_socket_context( struct us_loop_t *loop, int context_ext_size, - struct us_bun_socket_context_options_t options); + struct us_bun_socket_context_options_t options, + enum create_bun_socket_error_t *err); void us_internal_ssl_socket_context_free( us_internal_ssl_socket_context_r context); diff --git a/packages/bun-usockets/src/libusockets.h b/packages/bun-usockets/src/libusockets.h index b939af53efb20..e4a568cea1ca9 100644 --- a/packages/bun-usockets/src/libusockets.h +++ b/packages/bun-usockets/src/libusockets.h @@ -246,8 +246,16 @@ void *us_socket_context_get_native_handle(int ssl, us_socket_context_r context); /* A socket context holds shared callbacks and user data extension for associated sockets */ struct us_socket_context_t *us_create_socket_context(int ssl, us_loop_r loop, int ext_size, struct us_socket_context_options_t options) nonnull_fn_decl; + +enum create_bun_socket_error_t { + CREATE_BUN_SOCKET_ERROR_NONE = 0, + CREATE_BUN_SOCKET_ERROR_LOAD_CA_FILE, + CREATE_BUN_SOCKET_ERROR_INVALID_CA_FILE, + CREATE_BUN_SOCKET_ERROR_INVALID_CA, +}; + struct us_socket_context_t *us_create_bun_socket_context(int ssl, struct us_loop_t *loop, - int ext_size, struct us_bun_socket_context_options_t options); + int ext_size, struct us_bun_socket_context_options_t options, enum create_bun_socket_error_t *err); /* Delete resources allocated at creation time (will call unref now and only free when ref count == 0). */ void us_socket_context_free(int ssl, us_socket_context_r context) nonnull_fn_decl; diff --git a/packages/bun-uws/src/HttpContext.h b/packages/bun-uws/src/HttpContext.h index 338683f816890..0081779bdaf08 100644 --- a/packages/bun-uws/src/HttpContext.h +++ b/packages/bun-uws/src/HttpContext.h @@ -433,7 +433,8 @@ struct HttpContext { static HttpContext *create(Loop *loop, us_bun_socket_context_options_t options = {}) { HttpContext *httpContext; - httpContext = (HttpContext *) us_create_bun_socket_context(SSL, (us_loop_t *) loop, sizeof(HttpContextData), options); + enum create_bun_socket_error_t err = CREATE_BUN_SOCKET_ERROR_NONE; + httpContext = (HttpContext *) us_create_bun_socket_context(SSL, (us_loop_t *) loop, sizeof(HttpContextData), options, &err); if (!httpContext) { return nullptr; diff --git a/src/bun.js/api/bun/socket.zig b/src/bun.js/api/bun/socket.zig index 71b2061df6a69..0467cf9d9286b 100644 --- a/src/bun.js/api/bun/socket.zig +++ b/src/bun.js/api/bun/socket.zig @@ -649,11 +649,13 @@ pub const Listener = struct { vm.eventLoop().ensureWaker(); + var create_err: uws.create_bun_socket_error_t = .none; const socket_context = uws.us_create_bun_socket_context( @intFromBool(ssl_enabled), uws.Loop.get(), @sizeOf(usize), ctx_opts, + &create_err, ) orelse { var err = globalObject.createErrorInstance("Failed to listen on {s}:{d}", .{ hostname_or_unix.slice(), port orelse 0 }); defer { @@ -1180,7 +1182,8 @@ pub const Listener = struct { else .{}; - const socket_context = uws.us_create_bun_socket_context(@intFromBool(ssl_enabled), uws.Loop.get(), @sizeOf(usize), ctx_opts) orelse { + var create_err: uws.create_bun_socket_error_t = .none; + const socket_context = uws.us_create_bun_socket_context(@intFromBool(ssl_enabled), uws.Loop.get(), @sizeOf(usize), ctx_opts, &create_err) orelse { const err = JSC.SystemError{ .message = bun.String.static("Failed to connect"), .syscall = bun.String.static("connect"), diff --git a/src/bun.js/bindings/ScriptExecutionContext.cpp b/src/bun.js/bindings/ScriptExecutionContext.cpp index 06e5b7ddba82c..34534d6369b04 100644 --- a/src/bun.js/bindings/ScriptExecutionContext.cpp +++ b/src/bun.js/bindings/ScriptExecutionContext.cpp @@ -60,7 +60,8 @@ us_socket_context_t* ScriptExecutionContext::webSocketContextSSL() opts.request_cert = true; // but do not reject unauthorized opts.reject_unauthorized = false; - this->m_ssl_client_websockets_ctx = us_create_bun_socket_context(1, loop, sizeof(size_t), opts); + enum create_bun_socket_error_t err = CREATE_BUN_SOCKET_ERROR_NONE; + this->m_ssl_client_websockets_ctx = us_create_bun_socket_context(1, loop, sizeof(size_t), opts, &err); void** ptr = reinterpret_cast(us_socket_context_ext(1, m_ssl_client_websockets_ctx)); *ptr = this; registerHTTPContextForWebSocket(this, m_ssl_client_websockets_ctx, loop); diff --git a/src/cli/init_command.zig b/src/cli/init_command.zig index 16d4d407a701e..86f6efd224c11 100644 --- a/src/cli/init_command.zig +++ b/src/cli/init_command.zig @@ -21,10 +21,9 @@ const initializeStore = @import("./create_command.zig").initializeStore; const lex = bun.js_lexer; const logger = bun.logger; const JSPrinter = bun.js_printer; +const exists = bun.sys.exists; +const existsZ = bun.sys.existsZ; -fn exists(path: anytype) bool { - return bun.sys.exists(path); -} pub const InitCommand = struct { pub fn prompt( alloc: std.mem.Allocator, @@ -210,7 +209,7 @@ pub const InitCommand = struct { }; for (paths_to_try) |path| { - if (exists(path)) { + if (existsZ(path)) { fields.entry_point = bun.asByteSlice(path); break :infer; } @@ -279,16 +278,16 @@ pub const InitCommand = struct { var steps = Steps{}; - steps.write_gitignore = !exists(".gitignore"); + steps.write_gitignore = !existsZ(".gitignore"); - steps.write_readme = !exists("README.md") and !exists("README") and !exists("README.txt") and !exists("README.mdx"); + steps.write_readme = !existsZ("README.md") and !existsZ("README") and !existsZ("README.txt") and !existsZ("README.mdx"); steps.write_tsconfig = brk: { - if (exists("tsconfig.json")) { + if (existsZ("tsconfig.json")) { break :brk false; } - if (exists("jsconfig.json")) { + if (existsZ("jsconfig.json")) { break :brk false; } @@ -444,7 +443,7 @@ pub const InitCommand = struct { Output.flush(); - if (exists("package.json")) { + if (existsZ("package.json")) { var process = std.process.Child.init( &.{ try bun.selfExePath(), diff --git a/src/deps/uws.zig b/src/deps/uws.zig index 102858501ac26..3e3f92adf773d 100644 --- a/src/deps/uws.zig +++ b/src/deps/uws.zig @@ -2539,6 +2539,13 @@ pub const us_bun_socket_context_options_t = extern struct { }; pub extern fn create_ssl_context_from_bun_options(options: us_bun_socket_context_options_t) ?*BoringSSL.SSL_CTX; +pub const create_bun_socket_error_t = enum(i32) { + none = 0, + load_ca_file, + invalid_ca_file, + invalid_ca, +}; + pub const us_bun_verify_error_t = extern struct { error_no: i32 = 0, code: [*c]const u8 = null, @@ -2568,7 +2575,7 @@ pub extern fn us_socket_context_remove_server_name(ssl: i32, context: ?*SocketCo extern fn us_socket_context_on_server_name(ssl: i32, context: ?*SocketContext, cb: ?*const fn (?*SocketContext, [*c]const u8) callconv(.C) void) void; extern fn us_socket_context_get_native_handle(ssl: i32, context: ?*SocketContext) ?*anyopaque; pub extern fn us_create_socket_context(ssl: i32, loop: ?*Loop, ext_size: i32, options: us_socket_context_options_t) ?*SocketContext; -pub extern fn us_create_bun_socket_context(ssl: i32, loop: ?*Loop, ext_size: i32, options: us_bun_socket_context_options_t) ?*SocketContext; +pub extern fn us_create_bun_socket_context(ssl: i32, loop: ?*Loop, ext_size: i32, options: us_bun_socket_context_options_t, err: *create_bun_socket_error_t) ?*SocketContext; pub extern fn us_bun_socket_context_add_server_name(ssl: i32, context: ?*SocketContext, hostname_pattern: [*c]const u8, options: us_bun_socket_context_options_t, ?*anyopaque) void; pub extern fn us_socket_context_free(ssl: i32, context: ?*SocketContext) void; pub extern fn us_socket_context_ref(ssl: i32, context: ?*SocketContext) void; diff --git a/src/http.zig b/src/http.zig index 8c132ef505d1c..1057f248b9906 100644 --- a/src/http.zig +++ b/src/http.zig @@ -585,7 +585,14 @@ fn NewHTTPContext(comptime ssl: bool) type { bun.default_allocator.destroy(this); } - pub fn initWithClientConfig(this: *@This(), client: *HTTPClient) !void { + pub const InitError = error{ + FailedToOpenSocket, + LoadCAFile, + InvalidCAFile, + InvalidCA, + }; + + pub fn initWithClientConfig(this: *@This(), client: *HTTPClient) InitError!void { if (!comptime ssl) { @compileError("ssl only"); } @@ -595,13 +602,20 @@ fn NewHTTPContext(comptime ssl: bool) type { try this.initWithOpts(&opts); } - pub fn initWithOpts(this: *@This(), opts: *const uws.us_bun_socket_context_options_t) !void { + fn initWithOpts(this: *@This(), opts: *const uws.us_bun_socket_context_options_t) InitError!void { if (!comptime ssl) { @compileError("ssl only"); } - const socket = uws.us_create_bun_socket_context(ssl_int, http_thread.loop.loop, @sizeOf(usize), opts.*); + + var err: uws.create_bun_socket_error_t = .none; + const socket = uws.us_create_bun_socket_context(ssl_int, http_thread.loop.loop, @sizeOf(usize), opts.*, &err); if (socket == null) { - return error.FailedToOpenSocket; + return switch (err) { + .load_ca_file => error.LoadCAFile, + .invalid_ca_file => error.InvalidCAFile, + .invalid_ca => error.InvalidCA, + else => error.FailedToOpenSocket, + }; } this.us_socket_context = socket.?; this.sslCtx().setup(); @@ -614,7 +628,7 @@ fn NewHTTPContext(comptime ssl: bool) type { ); } - pub fn initWithThreadOpts(this: *@This(), init_opts: *const HTTPThread.InitOpts) !void { + pub fn initWithThreadOpts(this: *@This(), init_opts: *const HTTPThread.InitOpts) InitError!void { if (!comptime ssl) { @compileError("ssl only"); } @@ -630,7 +644,7 @@ fn NewHTTPContext(comptime ssl: bool) type { try this.initWithOpts(&opts); } - pub fn init(this: *@This()) !void { + pub fn init(this: *@This()) void { if (comptime ssl) { const opts: uws.us_bun_socket_context_options_t = .{ // we request the cert so we load root certs and can verify it @@ -638,7 +652,8 @@ fn NewHTTPContext(comptime ssl: bool) type { // we manually abort the connection if the hostname doesn't match .reject_unauthorized = 0, }; - this.us_socket_context = uws.us_create_bun_socket_context(ssl_int, http_thread.loop.loop, @sizeOf(usize), opts).?; + var err: uws.create_bun_socket_error_t = .none; + this.us_socket_context = uws.us_create_bun_socket_context(ssl_int, http_thread.loop.loop, @sizeOf(usize), opts, &err).?; this.sslCtx().setup(); } else { @@ -1074,8 +1089,28 @@ pub const HTTPThread = struct { } http_thread.loop = loop; - http_thread.http_context.init() catch @panic("Failed to init http context"); - http_thread.https_context.initWithThreadOpts(&opts) catch @panic("Failed to init https context"); + http_thread.http_context.init(); + http_thread.https_context.initWithThreadOpts(&opts) catch |err| { + switch (err) { + error.LoadCAFile => { + if (!bun.sys.existsZ(opts.abs_ca_file_name)) { + Output.err("HTTPThread", "failed to find CA file: '{s}'", .{opts.abs_ca_file_name}); + } else { + Output.err("HTTPThread", "failed to load CA file: '{s}'", .{opts.abs_ca_file_name}); + } + }, + error.InvalidCAFile => { + Output.err("HTTPThread", "the CA file is invalid: '{s}'", .{opts.abs_ca_file_name}); + }, + error.InvalidCA => { + Output.err("HTTPThread", "the provided CA is invalid", .{}); + }, + error.FailedToOpenSocket => { + Output.errGeneric("failed to start HTTP client thread", .{}); + }, + } + Global.crash(); + }; http_thread.has_awoken.store(true, .monotonic); http_thread.processEvents(); } diff --git a/src/sys.zig b/src/sys.zig index 731b8aa649ac7..c31f67d4a4e09 100644 --- a/src/sys.zig +++ b/src/sys.zig @@ -2480,6 +2480,16 @@ pub fn exists(path: []const u8) bool { @compileError("TODO: existsOSPath"); } +pub fn existsZ(path: [:0]const u8) bool { + if (comptime Environment.isPosix) { + return system.access(path, 0) == 0; + } + + if (comptime Environment.isWindows) { + return getFileAttributes(path) != null; + } +} + pub fn faccessat(dir_: anytype, subpath: anytype) JSC.Maybe(bool) { const has_sentinel = std.meta.sentinel(@TypeOf(subpath)) != null; const dir_fd = bun.toFD(dir_); From 78bf9516647c624832201ed0da9134d8c8d9e2d3 Mon Sep 17 00:00:00 2001 From: Dylan Conway Date: Tue, 8 Oct 2024 14:23:56 -0700 Subject: [PATCH 04/19] bunfig CA --- src/api/schema.zig | 7 ++++ src/bunfig.zig | 77 +++++++++++++++++++++++++++++------------ src/install/install.zig | 49 +++++++++++++++++--------- src/js_ast.zig | 9 +++++ 4 files changed, 103 insertions(+), 39 deletions(-) diff --git a/src/api/schema.zig b/src/api/schema.zig index a7b958c8a5ee5..1c3679be8d434 100644 --- a/src/api/schema.zig +++ b/src/api/schema.zig @@ -2974,6 +2974,13 @@ pub const Api = struct { /// concurrent_scripts concurrent_scripts: ?u32 = null, + cafile: ?[]const u8 = null, + + ca: ?union(enum) { + str: []const u8, + list: []const []const u8, + } = null, + pub fn decode(reader: anytype) anyerror!BunInstall { var this = std.mem.zeroes(BunInstall); diff --git a/src/bunfig.zig b/src/bunfig.zig index f141edcd2fec0..0ebfb9cb5dfa2 100644 --- a/src/bunfig.zig +++ b/src/bunfig.zig @@ -336,15 +336,15 @@ pub const Bunfig = struct { } if (comptime cmd.isNPMRelated() or cmd == .RunCommand or cmd == .AutoCommand) { - if (json.get("install")) |_bun| { + if (json.getObject("install")) |install_obj| { var install: *Api.BunInstall = this.ctx.install orelse brk: { - const install_ = try this.allocator.create(Api.BunInstall); - install_.* = std.mem.zeroes(Api.BunInstall); - this.ctx.install = install_; - break :brk install_; + const install = try this.allocator.create(Api.BunInstall); + install.* = std.mem.zeroes(Api.BunInstall); + this.ctx.install = install; + break :brk install; }; - if (_bun.get("auto")) |auto_install_expr| { + if (install_obj.get("auto")) |auto_install_expr| { if (auto_install_expr.data == .e_string) { this.ctx.debug.global_cache = options.GlobalCache.Map.get(auto_install_expr.asString(this.allocator) orelse "") orelse { try this.addError(auto_install_expr.loc, "Invalid auto install setting, must be one of true, false, or \"force\" \"fallback\" \"disable\""); @@ -361,13 +361,46 @@ pub const Bunfig = struct { } } - if (_bun.get("exact")) |exact| { + if (install_obj.get("cafile")) |cafile| { + install.cafile = try cafile.asStringCloned(allocator) orelse { + try this.addError(cafile.loc, "Invalid cafile. Expected a string."); + return; + }; + } + + if (install_obj.get("ca")) |ca| { + switch (ca.data) { + .e_array => |arr| { + var list = try allocator.alloc([]const u8, arr.items.len); + for (arr.items.slice(), 0..) |item, i| { + list[i] = try item.asStringCloned(allocator) orelse { + try this.addError(item.loc, "Invalid CA. Expected a string."); + return; + }; + } + install.ca = .{ + .list = list, + }; + }, + .e_string => |str| { + install.ca = .{ + .str = try str.stringCloned(allocator), + }; + }, + else => { + try this.addError(ca.loc, "Invalid CA. Expected a string or an array of strings."); + return; + }, + } + } + + if (install_obj.get("exact")) |exact| { if (exact.asBool()) |value| { install.exact = value; } } - if (_bun.get("prefer")) |prefer_expr| { + if (install_obj.get("prefer")) |prefer_expr| { try this.expectString(prefer_expr); if (Prefer.get(prefer_expr.asString(bun.default_allocator) orelse "")) |setting| { @@ -377,11 +410,11 @@ pub const Bunfig = struct { } } - if (_bun.get("registry")) |registry| { + if (install_obj.get("registry")) |registry| { install.default_registry = try this.parseRegistry(registry); } - if (_bun.get("scopes")) |scopes| { + if (install_obj.get("scopes")) |scopes| { var registry_map = install.scoped orelse Api.NpmRegistryMap{}; try this.expect(scopes, .e_object); @@ -399,32 +432,32 @@ pub const Bunfig = struct { install.scoped = registry_map; } - if (_bun.get("dryRun")) |dry_run| { + if (install_obj.get("dryRun")) |dry_run| { if (dry_run.asBool()) |value| { install.dry_run = value; } } - if (_bun.get("production")) |production| { + if (install_obj.get("production")) |production| { if (production.asBool()) |value| { install.production = value; } } - if (_bun.get("frozenLockfile")) |frozen_lockfile| { + if (install_obj.get("frozenLockfile")) |frozen_lockfile| { if (frozen_lockfile.asBool()) |value| { install.frozen_lockfile = value; } } - if (_bun.get("concurrentScripts")) |jobs| { + if (install_obj.get("concurrentScripts")) |jobs| { if (jobs.data == .e_number) { install.concurrent_scripts = jobs.data.e_number.toU32(); if (install.concurrent_scripts.? == 0) install.concurrent_scripts = null; } } - if (_bun.get("lockfile")) |lockfile_expr| { + if (install_obj.get("lockfile")) |lockfile_expr| { if (lockfile_expr.get("print")) |lockfile| { try this.expectString(lockfile); if (lockfile.asString(this.allocator)) |value| { @@ -457,41 +490,41 @@ pub const Bunfig = struct { } } - if (_bun.get("optional")) |optional| { + if (install_obj.get("optional")) |optional| { if (optional.asBool()) |value| { install.save_optional = value; } } - if (_bun.get("peer")) |optional| { + if (install_obj.get("peer")) |optional| { if (optional.asBool()) |value| { install.save_peer = value; } } - if (_bun.get("dev")) |optional| { + if (install_obj.get("dev")) |optional| { if (optional.asBool()) |value| { install.save_dev = value; } } - if (_bun.get("globalDir")) |dir| { + if (install_obj.get("globalDir")) |dir| { if (dir.asString(allocator)) |value| { install.global_dir = value; } } - if (_bun.get("globalBinDir")) |dir| { + if (install_obj.get("globalBinDir")) |dir| { if (dir.asString(allocator)) |value| { install.global_bin_dir = value; } } - if (_bun.get("logLevel")) |expr| { + if (install_obj.get("logLevel")) |expr| { try this.loadLogLevel(expr); } - if (_bun.get("cache")) |cache| { + if (install_obj.get("cache")) |cache| { load: { if (cache.asBool()) |value| { if (!value) { diff --git a/src/install/install.zig b/src/install/install.zig index 3effa453ce683..8d8e5dd3fa816 100644 --- a/src/install/install.zig +++ b/src/install/install.zig @@ -7090,8 +7090,8 @@ pub const PackageManager = struct { .password = "", .token = "", }; - if (bun_install_) |bun_install| { - if (bun_install.default_registry) |registry| { + if (bun_install_) |config| { + if (config.default_registry) |registry| { base = registry; } } @@ -7100,8 +7100,8 @@ pub const PackageManager = struct { defer { this.did_override_default_scope = this.scope.url_hash != Npm.Registry.default_url_hash; } - if (bun_install_) |bun_install| { - if (bun_install.scoped) |scoped| { + if (bun_install_) |config| { + if (config.scoped) |scoped| { for (scoped.scopes.keys(), scoped.scopes.values()) |name, *registry_| { var registry = registry_.*; if (registry.url.len == 0) registry.url = base.url; @@ -7109,42 +7109,57 @@ pub const PackageManager = struct { } } - if (bun_install.disable_cache orelse false) { + if (config.ca) |ca| { + switch (ca) { + .list => |ca_list| { + this.ca = ca_list; + }, + .str => |ca_str| { + this.ca = &.{ca_str}; + }, + } + } + + if (config.cafile) |cafile| { + this.ca_file_name = cafile; + } + + if (config.disable_cache orelse false) { this.enable.cache = false; } - if (bun_install.disable_manifest_cache orelse false) { + if (config.disable_manifest_cache orelse false) { this.enable.manifest_cache = false; } - if (bun_install.force orelse false) { + if (config.force orelse false) { this.enable.manifest_cache_control = false; this.enable.force_install = true; } - if (bun_install.save_yarn_lockfile orelse false) { + if (config.save_yarn_lockfile orelse false) { this.do.save_yarn_lock = true; } - if (bun_install.save_lockfile) |save_lockfile| { + if (config.save_lockfile) |save_lockfile| { this.do.save_lockfile = save_lockfile; this.enable.force_save_lockfile = true; } - if (bun_install.save_dev) |save| { + if (config.save_dev) |save| { this.local_package_features.dev_dependencies = save; } - if (bun_install.save_peer) |save| { + if (config.save_peer) |save| { this.do.install_peer_dependencies = save; this.remote_package_features.peer_dependencies = save; } - if (bun_install.exact) |exact| { + if (config.exact) |exact| { this.enable.exact_versions = exact; } - if (bun_install.production) |production| { + if (config.production) |production| { if (production) { this.local_package_features.dev_dependencies = false; this.enable.fail_early = true; @@ -7153,22 +7168,22 @@ pub const PackageManager = struct { } } - if (bun_install.frozen_lockfile) |frozen_lockfile| { + if (config.frozen_lockfile) |frozen_lockfile| { if (frozen_lockfile) { this.enable.frozen_lockfile = true; } } - if (bun_install.concurrent_scripts) |jobs| { + if (config.concurrent_scripts) |jobs| { this.max_concurrent_lifecycle_scripts = jobs; } - if (bun_install.save_optional) |save| { + if (config.save_optional) |save| { this.remote_package_features.optional_dependencies = save; this.local_package_features.optional_dependencies = save; } - this.explicit_global_directory = bun_install.global_dir orelse this.explicit_global_directory; + this.explicit_global_directory = config.global_dir orelse this.explicit_global_directory; } const default_disable_progress_bar: bool = brk: { diff --git a/src/js_ast.zig b/src/js_ast.zig index 363a000e6537d..d815627c45c80 100644 --- a/src/js_ast.zig +++ b/src/js_ast.zig @@ -3436,6 +3436,15 @@ pub const Expr = struct { return if (asProperty(expr, name)) |query| query.expr else null; } + pub fn getObject(expr: *const Expr, name: string) ?Expr { + if (expr.asProperty(name)) |query| { + if (query.expr.isObject()) { + return query.expr; + } + } + return null; + } + pub fn getString(expr: *const Expr, allocator: std.mem.Allocator, name: string) OOM!?struct { string, logger.Loc } { if (asProperty(expr, name)) |q| { if (q.expr.asString(allocator)) |str| { From e2d9bc6a9031eb91eb2b129ab37096ee49ea3525 Mon Sep 17 00:00:00 2001 From: Dylan Conway Date: Tue, 8 Oct 2024 14:46:22 -0700 Subject: [PATCH 05/19] one more --- src/sql/postgres.zig | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/sql/postgres.zig b/src/sql/postgres.zig index 93168b63e51cb..40b556ab707a7 100644 --- a/src/sql/postgres.zig +++ b/src/sql/postgres.zig @@ -3095,7 +3095,8 @@ pub const PostgresSQLConnection = struct { defer hostname.deinit(); if (tls_object.isEmptyOrUndefinedOrNull()) { const ctx = vm.rareData().postgresql_context.tcp orelse brk: { - const ctx_ = uws.us_create_bun_socket_context(0, vm.uwsLoop(), @sizeOf(*PostgresSQLConnection), uws.us_bun_socket_context_options_t{}).?; + var err: uws.create_bun_socket_error_t = .none; + const ctx_ = uws.us_create_bun_socket_context(0, vm.uwsLoop(), @sizeOf(*PostgresSQLConnection), uws.us_bun_socket_context_options_t{}, &err).?; uws.NewSocketHandler(false).configure(ctx_, true, *PostgresSQLConnection, SocketHandler(false)); vm.rareData().postgresql_context.tcp = ctx_; break :brk ctx_; From abc43544dda862eae6e61729e91b99a8d7c70533 Mon Sep 17 00:00:00 2001 From: Dylan Conway Date: Tue, 8 Oct 2024 19:24:13 -0700 Subject: [PATCH 06/19] cleanup output --- src/Progress.zig | 4 +- src/cli/pack_command.zig | 2 +- src/cli/package_manager_command.zig | 2 +- src/cli/publish_command.zig | 4 +- src/http.zig | 62 +-- src/install/bin.zig | 1 - src/install/install.zig | 555 ++++++++++++++---------- src/install/lifecycle_script_runner.zig | 4 + 8 files changed, 364 insertions(+), 270 deletions(-) diff --git a/src/Progress.zig b/src/Progress.zig index 822feb7576b5c..d8cad42ba3f38 100644 --- a/src/Progress.zig +++ b/src/Progress.zig @@ -380,11 +380,11 @@ pub fn lock_stderr(p: *Progress) void { p.terminal = null; }; } - std.debug.getStderrMutex().lock(); + std.debug.lockStdErr(); } pub fn unlock_stderr(p: *Progress) void { - std.debug.getStderrMutex().unlock(); + std.debug.unlockStdErr(); p.update_mutex.unlock(); } diff --git a/src/cli/pack_command.zig b/src/cli/pack_command.zig index 4d4bc36b40590..b6a1b87b39989 100644 --- a/src/cli/pack_command.zig +++ b/src/cli/pack_command.zig @@ -1959,7 +1959,7 @@ pub const PackCommand = struct { } // only produce this error only when we need to get the workspace version - Output.errGeneric("Failed to resolve workspace version for \"{s}\" in `{s}`. Run `bun install` and try again.", .{ + Output.errGeneric("Failed to resolve workspace version for \"{s}\" in `{s}`. Run `bun install` and try again.", .{ dependency_name, dependency_group, }); diff --git a/src/cli/package_manager_command.zig b/src/cli/package_manager_command.zig index 801f4929360d0..c0fd615f4501e 100644 --- a/src/cli/package_manager_command.zig +++ b/src/cli/package_manager_command.zig @@ -159,7 +159,7 @@ pub const PackageManagerCommand = struct { switch (err) { error.OutOfMemory => bun.outOfMemory(), error.NeedAuth => { - Output.errGeneric("missing authentication (run `bunx npm login`)", .{}); + Output.errGeneric("missing authentication (run `bunx npm login`)", .{}); }, error.ProbablyInvalidAuth => { Output.errGeneric("failed to authenticate with registry '{}'", .{ diff --git a/src/cli/publish_command.zig b/src/cli/publish_command.zig index c54903373a09d..694d7f15688e0 100644 --- a/src/cli/publish_command.zig +++ b/src/cli/publish_command.zig @@ -372,7 +372,7 @@ pub const PublishCommand = struct { switch (err) { error.OutOfMemory => bun.outOfMemory(), error.NeedAuth => { - Output.errGeneric("missing authentication (run `bunx npm login`)", .{}); + Output.errGeneric("missing authentication (run `bunx npm login`)", .{}); Global.crash(); }, } @@ -419,7 +419,7 @@ pub const PublishCommand = struct { switch (err) { error.OutOfMemory => bun.outOfMemory(), error.NeedAuth => { - Output.errGeneric("missing authentication (run `bunx npm login`)", .{}); + Output.errGeneric("missing authentication (run `bunx npm login`)", .{}); Global.crash(); }, } diff --git a/src/http.zig b/src/http.zig index 1057f248b9906..075c1d2dc0ace 100644 --- a/src/http.zig +++ b/src/http.zig @@ -34,6 +34,7 @@ const Progress = bun.Progress; const X509 = @import("./bun.js/api/bun/x509.zig"); const SSLConfig = @import("./bun.js/api/server.zig").ServerConfig.SSLConfig; const SSLWrapper = @import("./bun.js/api/bun/ssl_wrapper.zig").SSLWrapper; +const PackageManager = bun.install.PackageManager; const URLBufferPool = ObjectPool([8192]u8, null, false, 10); const uws = bun.uws; @@ -516,6 +517,13 @@ pub const HTTPCertError = struct { reason: [:0]const u8 = "", }; +pub const InitError = error{ + FailedToOpenSocket, + LoadCAFile, + InvalidCAFile, + InvalidCA, +}; + fn NewHTTPContext(comptime ssl: bool) type { return struct { const pool_size = 64; @@ -585,13 +593,6 @@ fn NewHTTPContext(comptime ssl: bool) type { bun.default_allocator.destroy(this); } - pub const InitError = error{ - FailedToOpenSocket, - LoadCAFile, - InvalidCAFile, - InvalidCA, - }; - pub fn initWithClientConfig(this: *@This(), client: *HTTPClient) InitError!void { if (!comptime ssl) { @compileError("ssl only"); @@ -1043,9 +1044,34 @@ pub const HTTPThread = struct { return this.lazy_libdeflater.?; } + fn onInitErrorNoop(err: InitError, opts: InitOpts) noreturn { + switch (err) { + error.LoadCAFile => { + if (!bun.sys.existsZ(opts.abs_ca_file_name)) { + Output.err("HTTPThread", "failed to find CA file: '{s}'", .{opts.abs_ca_file_name}); + } else { + Output.err("HTTPThread", "failed to load CA file: '{s}'", .{opts.abs_ca_file_name}); + } + }, + error.InvalidCAFile => { + Output.err("HTTPThread", "the CA file is invalid: '{s}'", .{opts.abs_ca_file_name}); + }, + error.InvalidCA => { + Output.err("HTTPThread", "the provided CA is invalid", .{}); + }, + error.FailedToOpenSocket => { + Output.errGeneric("failed to start HTTP client thread", .{}); + }, + } + Global.crash(); + } + pub const InitOpts = struct { ca: []stringZ = &.{}, abs_ca_file_name: stringZ = &.{}, + for_install: bool = false, + + onInitError: *const fn (err: InitError, opts: InitOpts) noreturn = &onInitErrorNoop, }; fn initOnce(opts: *const InitOpts) void { @@ -1090,27 +1116,7 @@ pub const HTTPThread = struct { http_thread.loop = loop; http_thread.http_context.init(); - http_thread.https_context.initWithThreadOpts(&opts) catch |err| { - switch (err) { - error.LoadCAFile => { - if (!bun.sys.existsZ(opts.abs_ca_file_name)) { - Output.err("HTTPThread", "failed to find CA file: '{s}'", .{opts.abs_ca_file_name}); - } else { - Output.err("HTTPThread", "failed to load CA file: '{s}'", .{opts.abs_ca_file_name}); - } - }, - error.InvalidCAFile => { - Output.err("HTTPThread", "the CA file is invalid: '{s}'", .{opts.abs_ca_file_name}); - }, - error.InvalidCA => { - Output.err("HTTPThread", "the provided CA is invalid", .{}); - }, - error.FailedToOpenSocket => { - Output.errGeneric("failed to start HTTP client thread", .{}); - }, - } - Global.crash(); - }; + http_thread.https_context.initWithThreadOpts(&opts) catch |err| opts.onInitError(err, opts); http_thread.has_awoken.store(true, .monotonic); http_thread.processEvents(); } diff --git a/src/install/bin.zig b/src/install/bin.zig index 8361b90bc9106..dc792ac944111 100644 --- a/src/install/bin.zig +++ b/src/install/bin.zig @@ -2,7 +2,6 @@ const ExternalStringList = @import("./install.zig").ExternalStringList; const Semver = @import("./semver.zig"); const ExternalString = Semver.ExternalString; const String = Semver.String; -const Output = bun.Output; const Global = bun.Global; const std = @import("std"); const strings = bun.strings; diff --git a/src/install/install.zig b/src/install/install.zig index 8d8e5dd3fa816..0179ec3e7a912 100644 --- a/src/install/install.zig +++ b/src/install/install.zig @@ -677,6 +677,8 @@ pub const Task = struct { pt.apply() catch bun.outOfMemory(); if (pt.callback.apply.logger.errors > 0) { defer pt.callback.apply.logger.deinit(); + manager.pauseProgress(); + defer manager.resumeProgress(); // this.log.addErrorFmt(null, logger.Loc.Empty, bun.default_allocator, "failed to apply patch: {}", .{e}) catch unreachable; pt.callback.apply.logger.printForLogLevel(Output.writer()) catch {}; } @@ -1624,9 +1626,9 @@ pub fn NewPackageInstall(comptime kind: PkgInstallKind) type { } if (bun.windows.Win32Error.get().toSystemErrno()) |err| { - Output.prettyError("{s}: copying file {}", .{ @tagName(err), bun.fmt.fmtOSPath(entry.path, .{}) }); + PackageManager.instance.cleanError(err, "copying file {}", .{bun.fmt.fmtOSPath(entry.path, .{})}); } else { - Output.prettyError("error copying file {}", .{bun.fmt.fmtOSPath(entry.path, .{})}); + PackageManager.instance.cleanErrorGeneric("copying file {}", .{bun.fmt.fmtOSPath(entry.path, .{})}); } Global.crash(); @@ -1654,7 +1656,7 @@ pub fn NewPackageInstall(comptime kind: PkgInstallKind) type { progress_.refresh(); } - Output.prettyErrorln("{s}: copying file {}", .{ @errorName(err), bun.fmt.fmtOSPath(entry.path, .{}) }); + PackageManager.instance.cleanError(err, "copying file {}", .{bun.fmt.fmtOSPath(entry.path, .{})}); Global.crash(); }; }; @@ -1671,7 +1673,7 @@ pub fn NewPackageInstall(comptime kind: PkgInstallKind) type { progress_.refresh(); } - Output.prettyError("{s}: copying file {}", .{ @errorName(err), bun.fmt.fmtOSPath(entry.path, .{}) }); + PackageManager.instance.cleanError(err, "copying file {}", .{bun.fmt.fmtOSPath(entry.path, .{})}); Global.crash(); }; } @@ -2039,7 +2041,7 @@ pub fn NewPackageInstall(comptime kind: PkgInstallKind) type { }.get(); if (once_log) { - Output.warn("CreateHardLinkW failed, falling back to CopyFileW: {} -> {}\n", .{ + PackageManager.instance.cleanWarnResume("CreateHardLinkW failed, falling back to CopyFileW: {} -> {}\n", .{ bun.fmt.fmtOSPath(src, .{}), bun.fmt.fmtOSPath(dest, .{}), }); @@ -6339,10 +6341,12 @@ pub const PackageManager = struct { } if (comptime log_level.isVerbose()) { + manager.pauseProgress(); Output.prettyError(" ", .{}); Output.printElapsed(@as(f64, @floatFromInt(task.http.elapsed)) / std.time.ns_per_ms); Output.prettyError("\nDownloaded {s} versions\n", .{name.slice()}); Output.flush(); + manager.resumeProgress(); } if (response.status_code == 304) { @@ -6542,10 +6546,12 @@ pub const PackageManager = struct { } if (comptime log_level.isVerbose()) { + manager.pauseProgress(); Output.prettyError(" ", .{}); Output.printElapsed(@as(f64, @floatCast(@as(f64, @floatFromInt(task.http.elapsed)) / std.time.ns_per_ms))); Output.prettyError(" Downloaded {s} tarball\n", .{extract.name.slice()}); Output.flush(); + manager.resumeProgress(); } if (comptime log_level.showProgress()) { @@ -6571,7 +6577,9 @@ pub const PackageManager = struct { if (task.log.msgs.items.len > 0) { switch (Output.enable_ansi_colors) { inline else => |enable_ansi_colors| { + manager.pauseProgress(); try task.log.printForLogLevelWithEnableAnsiColors(Output.errorWriter(), enable_ansi_colors); + manager.resumeProgress(); }, } } @@ -6895,6 +6903,59 @@ pub const PackageManager = struct { } } + pub inline fn pauseProgress(this: *PackageManager) void { + if (this.options.log_level.showProgress() and Output.enable_ansi_colors) { + this.progress.lock_stderr(); + } + } + + pub inline fn resumeProgress(this: *PackageManager) void { + if (this.options.log_level.showProgress() and Output.enable_ansi_colors) { + this.progress.unlock_stderr(); + } + } + + /// Will pause and clear progrss from terminal, print the error, then resume progress. Best for errors that + /// are not immediately fatal. + pub inline fn cleanErrorGenericResume(this: *PackageManager, comptime fmt: string, args: anytype) void { + this.pauseProgress(); + Output.errGeneric(fmt, args); + this.resumeProgress(); + } + + /// Will pause and clear progrss from terminal, print the error, then resume progress. Best for errors that + /// are not immediately fatal. + pub inline fn cleanErrorResume(this: *PackageManager, name: anytype, comptime fmt: string, args: anytype) void { + this.pauseProgress(); + Output.err(name, fmt, args); + this.resumeProgress(); + } + + /// Will pause and clear progress from terminal if it exists. Does not resume progress, best for calling + /// before `Global.crash()`. + pub inline fn cleanErrorGeneric(this: *PackageManager, comptime fmt: string, args: anytype) void { + this.pauseProgress(); + Output.errGeneric(fmt, args); + } + + /// Will pause and clear progress from terminal if it exists. Does not resume progress, best for calling + /// before `Global.crash()`. + pub inline fn cleanError(this: *PackageManager, name: anytype, comptime fmt: string, args: anytype) void { + this.pauseProgress(); + Output.err(name, fmt, args); + } + + pub inline fn cleanWarnResume(this: *PackageManager, comptime fmt: string, args: anytype) void { + this.pauseProgress(); + Output.warn(fmt, args); + this.resumeProgress(); + } + + pub inline fn cleanWarn(this: *PackageManager, comptime fmt: string, args: anytype) void { + this.pauseProgress(); + Output.warn(fmt, args); + } + pub const Options = struct { log_level: LogLevel = .default, global: bool = false, @@ -8354,6 +8415,28 @@ pub const PackageManager = struct { } }; + fn httpThreadOnInitError(err: HTTP.InitError, opts: HTTP.HTTPThread.InitOpts) noreturn { + switch (err) { + error.LoadCAFile => { + if (!bun.sys.existsZ(opts.abs_ca_file_name)) { + PackageManager.instance.cleanError("HTTPThread", "failed to find CA file: '{s}'", .{opts.abs_ca_file_name}); + } else { + PackageManager.instance.cleanError("HTTPThread", "failed to load CA file: '{s}'", .{opts.abs_ca_file_name}); + } + }, + error.InvalidCAFile => { + PackageManager.instance.cleanError("HTTPThread", "the CA file is invalid: '{s}'", .{opts.abs_ca_file_name}); + }, + error.InvalidCA => { + PackageManager.instance.cleanError("HTTPThread", "the provided CA is invalid", .{}); + }, + error.FailedToOpenSocket => { + PackageManager.instance.cleanErrorGeneric("failed to start HTTP client thread", .{}); + }, + } + Global.crash(); + } + pub fn init( ctx: Command.Context, cli: CommandLineArguments, @@ -8660,7 +8743,6 @@ pub const PackageManager = struct { .resolve_tasks = .{}, .lockfile = undefined, .root_package_json_file = root_package_json_file, - // .progress .event_loop = .{ .mini = JSC.MiniEventLoop.init(bun.default_allocator), }, @@ -8726,6 +8808,7 @@ pub const PackageManager = struct { HTTP.HTTPThread.init(&.{ .ca = ca, .abs_ca_file_name = abs_ca_file_name, + .onInitError = &httpThreadOnInitError, }); manager.timestamp_for_manifest_cache_control = brk: { @@ -8801,7 +8884,7 @@ pub const PackageManager = struct { }; manager.lockfile = try allocator.create(Lockfile); - if (Output.enable_ansi_colors_stderr) { + if (Output.enable_ansi_colors) { manager.progress = Progress{}; manager.progress.supports_ansi_escape_codes = Output.enable_ansi_colors_stderr; manager.root_progress_node = manager.progress.start("", 0); @@ -8965,7 +9048,7 @@ pub const PackageManager = struct { // Step 1. parse the nearest package.json file { const package_json_source = bun.sys.File.toSource(manager.original_package_json_path, ctx.allocator).unwrap() catch |err| { - Output.errGeneric("failed to read \"{s}\" for linking: {s}", .{ manager.original_package_json_path, @errorName(err) }); + manager.cleanErrorGeneric("failed to read \"{s}\" for linking: {s}", .{ manager.original_package_json_path, @errorName(err) }); Global.crash(); }; lockfile.initEmpty(ctx.allocator); @@ -9067,7 +9150,7 @@ pub const PackageManager = struct { .node_modules = bun.toFD(node_modules.fd), .node_modules_path = bun.getFdPath(node_modules, &node_modules_path_buf) catch |err| { if (manager.options.log_level != .silent) { - Output.err(err, "failed to link binary", .{}); + manager.cleanError(err, "failed to link binary", .{}); } Global.crash(); }, @@ -9148,7 +9231,7 @@ pub const PackageManager = struct { // Step 1. parse the nearest package.json file { const package_json_source = bun.sys.File.toSource(manager.original_package_json_path, ctx.allocator).unwrap() catch |err| { - Output.errGeneric("failed to read \"{s}\" for unlinking: {s}", .{ manager.original_package_json_path, @errorName(err) }); + manager.cleanErrorGeneric("failed to read \"{s}\" for unlinking: {s}", .{ manager.original_package_json_path, @errorName(err) }); Global.crash(); }; lockfile.initEmpty(ctx.allocator); @@ -9157,12 +9240,12 @@ pub const PackageManager = struct { name = lockfile.str(&package.name); if (name.len == 0) { if (manager.options.log_level != .silent) { - Output.prettyErrorln("error: package.json missing \"name\" in \"{s}\"", .{package_json_source.path.text}); + manager.cleanErrorGeneric("package.json missing \"name\" in \"{s}\"", .{package_json_source.path.text}); } Global.crash(); } else if (!strings.isNPMPackageName(name)) { if (manager.options.log_level != .silent) { - Output.prettyErrorln("error: invalid package.json name \"{s}\" in \"{s}\"", .{ + manager.cleanErrorGeneric("invalid package.json name \"{s}\" in \"{s}\"", .{ name, package_json_source.path.text, }); @@ -9197,7 +9280,7 @@ pub const PackageManager = struct { break :brk manager.global_dir.?.makeOpenPath("node_modules", .{}) catch |err| { if (manager.options.log_level != .silent) - Output.prettyErrorln("error: failed to create node_modules in global dir due to error {s}", .{@errorName(err)}); + manager.cleanErrorGeneric("failed to create node_modules in global dir due to error {s}", .{@errorName(err)}); Global.crash(); }; }; @@ -9234,14 +9317,14 @@ pub const PackageManager = struct { // delete it if it exists node_modules.deleteTree(name) catch |err| { if (manager.options.log_level != .silent) - Output.prettyErrorln("error: failed to unlink package in global dir due to error {s}", .{@errorName(err)}); + manager.cleanErrorGeneric("failed to unlink package in global dir due to error {s}", .{@errorName(err)}); Global.crash(); }; Output.prettyln("success: unlinked package \"{s}\"", .{name}); Global.exit(0); } else { - Output.prettyln("error: bun unlink {{packageName}} not implemented yet", .{}); + manager.cleanErrorGeneric("bun unlink {{packageName}} not implemented yet", .{}); Global.crash(); } } @@ -10323,14 +10406,14 @@ pub const PackageManager = struct { if (manager.options.positionals.len <= 1) { switch (manager.subcommand) { .add => { - Output.errGeneric("no package specified to add", .{}); + manager.cleanErrorGeneric("no package specified to add", .{}); Output.flush(); PackageManager.CommandLineArguments.printHelp(.add); Global.exit(0); }, .remove => { - Output.errGeneric("no package specified to remove", .{}); + manager.cleanErrorGeneric("no package specified to remove", .{}); Output.flush(); PackageManager.CommandLineArguments.printHelp(.remove); @@ -10385,14 +10468,14 @@ pub const PackageManager = struct { manager.log.printForLogLevelWithEnableAnsiColors(Output.errorWriter(), enable_ansi_colors) catch {}; }, } - Output.errGeneric("failed to parse package.json \"{s}\": {s}", .{ + manager.cleanErrorGeneric("failed to parse package.json \"{s}\": {s}", .{ manager.original_package_json_path, @errorName(err), }); Global.crash(); }, .read_err => |err| { - Output.errGeneric("failed to read package.json \"{s}\": {s}", .{ + manager.cleanErrorGeneric("failed to read package.json \"{s}\": {s}", .{ manager.original_package_json_path, @errorName(err), }); @@ -10410,10 +10493,10 @@ pub const PackageManager = struct { if (subcommand == .remove) { if (current_package_json.root.data != .e_object) { - Output.errGeneric("package.json is not an Object {{}}, so there's nothing to {s}!", .{@tagName(subcommand)}); + manager.cleanErrorGeneric("package.json is not an Object {{}}, so there's nothing to {s}!", .{@tagName(subcommand)}); Global.crash(); } else if (current_package_json.root.data.e_object.properties.len == 0) { - Output.errGeneric("package.json is empty {{}}, so there's nothing to {s}!", .{@tagName(subcommand)}); + manager.cleanErrorGeneric("package.json is empty {{}}, so there's nothing to {s}!", .{@tagName(subcommand)}); Global.crash(); } else if (current_package_json.root.asProperty("devDependencies") == null and current_package_json.root.asProperty("dependencies") == null and @@ -10546,7 +10629,7 @@ pub const PackageManager = struct { .indent = current_package_json_indent, }, ) catch |err| { - Output.prettyErrorln("package.json failed to write due to error {s}", .{@errorName(err)}); + manager.cleanErrorGeneric("package.json failed to write due to error {s}", .{@errorName(err)}); Global.crash(); }; @@ -10589,14 +10672,14 @@ pub const PackageManager = struct { manager.log.printForLogLevelWithEnableAnsiColors(Output.errorWriter(), enable_ansi_colors) catch {}; }, } - Output.errGeneric("failed to parse package.json \"{s}\": {s}", .{ + manager.cleanErrorGeneric("failed to parse package.json \"{s}\": {s}", .{ root_package_json_path, @errorName(err), }); Global.crash(); }, .read_err => |err| { - Output.errGeneric("failed to read package.json \"{s}\": {s}", .{ + manager.cleanErrorGeneric("failed to read package.json \"{s}\": {s}", .{ manager.original_package_json_path, @errorName(err), }); @@ -10626,7 +10709,7 @@ pub const PackageManager = struct { .indent = root_package_json.indentation, }, ) catch |err| { - Output.prettyErrorln("package.json failed to write due to error {s}", .{@errorName(err)}); + manager.cleanErrorGeneric("package.json failed to write due to error {s}", .{@errorName(err)}); Global.crash(); }; root_package_json.source.contents = try manager.allocator.dupe(u8, package_json_writer2.ctx.writtenWithoutTrailingZero()); @@ -10650,7 +10733,7 @@ pub const PackageManager = struct { // Now, we _re_ parse our in-memory edited package.json // so we can commit the version we changed from the lockfile var new_package_json = JSON.parsePackageJSONUTF8(&source, manager.log, manager.allocator) catch |err| { - Output.prettyErrorln("package.json failed to parse due to error {s}", .{@errorName(err)}); + manager.cleanErrorGeneric("package.json failed to parse due to error {s}", .{@errorName(err)}); Global.crash(); }; @@ -10689,7 +10772,7 @@ pub const PackageManager = struct { .indent = current_package_json_indent, }, ) catch |err| { - Output.prettyErrorln("package.json failed to write due to error {s}", .{@errorName(err)}); + manager.cleanErrorGeneric("package.json failed to write due to error {s}", .{@errorName(err)}); Global.crash(); }; @@ -10766,7 +10849,7 @@ pub const PackageManager = struct { } } else |err| { if (err != error.ENOENT) { - Output.err(err, "while reading node_modules/.bin", .{}); + manager.cleanError(err, "while reading node_modules/.bin", .{}); Global.crash(); } } @@ -10796,6 +10879,7 @@ pub const PackageManager = struct { const IdPair = struct { DependencyID, PackageID }; fn pkgInfoForNameAndVersion( + this: *PackageManager, lockfile: *Lockfile, iterator: *Lockfile.Tree.Iterator, pkg_maybe_version_to_patch: []const u8, @@ -10829,7 +10913,7 @@ pub const PackageManager = struct { } if (pairs.items.len == 0) { - Output.prettyErrorln("\nerror: package {s} not found", .{pkg_maybe_version_to_patch}); + this.cleanErrorGeneric("package {s} not found", .{pkg_maybe_version_to_patch}); Global.crash(); return; } @@ -10839,8 +10923,8 @@ pub const PackageManager = struct { if (pairs.items.len == 1) { const dep_id, const pkg_id = pairs.items[0]; const folder = (try nodeModulesFolderForDependencyID(iterator, dep_id)) orelse { - Output.prettyError( - "error: could not find the folder for {s} in node_modules\n", + this.cleanErrorGeneric( + "could not find the folder for {s} in node_modules", .{pkg_maybe_version_to_patch}, ); Global.crash(); @@ -10856,8 +10940,8 @@ pub const PackageManager = struct { // so we are going to try looking for each dep id in node_modules _, const pkg_id = pairs.items[0]; const folder = (try nodeModulesFolderForDependencyIDs(iterator, pairs.items)) orelse { - Output.prettyError( - "error: could not find the folder for {s} in node_modules\n", + this.cleanErrorGeneric( + "could not find the folder for {s} in node_modules", .{pkg_maybe_version_to_patch}, ); Global.crash(); @@ -10875,8 +10959,8 @@ pub const PackageManager = struct { if (pairs.items.len == 1) { const dep_id, const pkg_id = pairs.items[0]; const folder = (try nodeModulesFolderForDependencyID(iterator, dep_id)) orelse { - Output.prettyError( - "error: could not find the folder for {s} in node_modules\n", + this.cleanErrorGeneric( + "could not find the folder for {s} in node_modules", .{pkg_maybe_version_to_patch}, ); Global.crash(); @@ -10906,8 +10990,8 @@ pub const PackageManager = struct { if (count == pairs.items.len) { // It may be hoisted, so we'll try the first one that matches const folder = (try nodeModulesFolderForDependencyIDs(iterator, pairs.items)) orelse { - Output.prettyError( - "error: could not find the folder for {s} in node_modules\n", + this.cleanErrorGeneric( + "could not find the folder for {s} in node_modules", .{pkg_maybe_version_to_patch}, ); Global.crash(); @@ -11004,8 +11088,8 @@ pub const PackageManager = struct { switch (bun.sys.File.toSource(package_json_path, manager.allocator)) { .result => |s| break :src s, .err => |e| { - Output.prettyError( - "error: failed to read package.json: {}\n", + manager.cleanErrorGeneric( + "failed to read package.json: {}", .{e.withPath(package_json_path).toSystemError()}, ); Global.crash(); @@ -11016,6 +11100,7 @@ pub const PackageManager = struct { initializeStore(); const json = JSON.parsePackageJSONUTF8AlwaysDecode(&package_json_source, manager.log, manager.allocator) catch |err| { + manager.pauseProgress(); switch (Output.enable_ansi_colors) { inline else => |enable_ansi_colors| { manager.log.printForLogLevelWithEnableAnsiColors(Output.errorWriter(), enable_ansi_colors) catch {}; @@ -11029,8 +11114,8 @@ pub const PackageManager = struct { if (json.asProperty("version")) |v| { if (v.expr.asString(manager.allocator)) |s| break :version s; } - Output.prettyError( - "error: invalid package.json, missing or invalid property \"version\": {s}\n", + manager.cleanErrorGeneric( + "invalid package.json, missing or invalid property \"version\": {s}", .{package_json_source.path.text}, ); Global.crash(); @@ -11056,7 +11141,7 @@ pub const PackageManager = struct { break :id pkg; } } - Output.prettyError("error: could not find package with name: {s}\n", .{ + manager.cleanErrorGeneric("could not find package with name: {s}", .{ package.name.slice(lockfile.buffers.string_bytes.items), }); Global.crash(); @@ -11096,7 +11181,7 @@ pub const PackageManager = struct { .name_and_version => brk: { const pkg_maybe_version_to_patch = argument; const name, const version = Dependency.splitNameAndVersion(pkg_maybe_version_to_patch); - const pkg_id, const folder = pkgInfoForNameAndVersion(manager.lockfile, &iterator, pkg_maybe_version_to_patch, name, version); + const pkg_id, const folder = pkgInfoForNameAndVersion(manager, manager.lockfile, &iterator, pkg_maybe_version_to_patch, name, version); const pkg = manager.lockfile.packages.get(pkg_id); const pkg_name = pkg.name.slice(strbuf); @@ -11140,8 +11225,8 @@ pub const PackageManager = struct { // // So we will overwrite the folder by directly copying the package in cache into it manager.overwritePackageInNodeModulesFolder(cache_dir, cache_dir_subpath, module_folder) catch |e| { - Output.prettyError( - "error: error overwriting folder in node_modules: {s}\n", + manager.cleanErrorGeneric( + "error overwriting folder in node_modules: {s}", .{@errorName(e)}, ); Global.crash(); @@ -11165,165 +11250,133 @@ pub const PackageManager = struct { cache_dir_subpath: []const u8, node_modules_folder_path: []const u8, ) !void { - var node_modules_folder = try std.fs.cwd().openDir(node_modules_folder_path, .{ .iterate = true }); - defer node_modules_folder.close(); + var destination_dir = try std.fs.cwd().openDir(node_modules_folder_path, .{ .iterate = true }); + defer destination_dir.close(); - const IGNORED_PATHS: []const bun.OSPathSlice = &[_][]const bun.OSPathChar{ + const ignored_paths: []const bun.OSPathSlice = &[_][]const bun.OSPathChar{ bun.OSPathLiteral("node_modules"), bun.OSPathLiteral(".git"), bun.OSPathLiteral("CMakeFiles"), }; - const FileCopier = struct { - pub fn copy( - destination_dir_: std.fs.Dir, - walker: *Walker, - in_dir: if (bun.Environment.isWindows) []const u16 else void, - out_dir: if (bun.Environment.isWindows) []const u16 else void, - buf1: if (bun.Environment.isWindows) []u16 else void, - buf2: if (bun.Environment.isWindows) []u16 else void, - tmpdir_in_node_modules: if (bun.Environment.isWindows) std.fs.Dir else void, - ) !u32 { - var real_file_count: u32 = 0; - - var copy_file_state: bun.CopyFileState = .{}; - var pathbuf: bun.PathBuffer = undefined; - var pathbuf2: bun.PathBuffer = undefined; - // _ = pathbuf; // autofix - - while (try walker.next()) |entry| { - if (entry.kind != .file) continue; - real_file_count += 1; - const openFile = std.fs.Dir.openFile; - const createFile = std.fs.Dir.createFile; - - // 1. rename original file in node_modules to tmp_dir_in_node_modules - // 2. create the file again - // 3. copy cache flie to the newly re-created file - // 4. profit - if (comptime bun.Environment.isWindows) { - var tmpbuf: [1024]u8 = undefined; - const basename = bun.strings.fromWPath(pathbuf2[0..], entry.basename); - const tmpname = bun.span(bun.fs.FileSystem.instance.tmpname(basename, tmpbuf[0..], bun.fastRandom()) catch |e| { - Output.prettyError("error: copying file {s}", .{@errorName(e)}); - Global.crash(); - }); - - const entrypath = bun.strings.fromWPath(pathbuf[0..], entry.path); - pathbuf[entrypath.len] = 0; - const entrypathZ = pathbuf[0..entrypath.len :0]; - - if (bun.sys.renameatConcurrently( - bun.toFD(destination_dir_.fd), - entrypathZ, - bun.toFD(tmpdir_in_node_modules.fd), - tmpname, - .{ .move_fallback = true }, - ).asErr()) |e| { - Output.prettyError("error: copying file {}", .{e}); - Global.crash(); - } - - var outfile = createFile(destination_dir_, entrypath, .{}) catch |e| { - Output.prettyError("error: failed to create file {s} ({s})", .{ entrypath, @errorName(e) }); - Global.crash(); - }; - outfile.close(); - - const infile_path = bun.path.joinStringBufWZ(buf1, &[_][]const u16{ in_dir, entry.path }, .auto); - const outfile_path = bun.path.joinStringBufWZ(buf2, &[_][]const u16{ out_dir, entry.path }, .auto); - - bun.copyFileWithState(infile_path, outfile_path, ©_file_state).unwrap() catch |err| { - Output.prettyError("{s}: copying file {}", .{ @errorName(err), bun.fmt.fmtOSPath(entry.path, .{}) }); - Global.crash(); - }; - } else if (comptime Environment.isPosix) { - var in_file = try openFile(entry.dir, entry.basename, .{ .mode = .read_only }); - defer in_file.close(); - - @memcpy(pathbuf[0..entry.path.len], entry.path); - pathbuf[entry.path.len] = 0; - - if (bun.sys.unlinkat( - bun.toFD(destination_dir_.fd), - pathbuf[0..entry.path.len :0], - ).asErr()) |e| { - Output.prettyError("error: copying file {}", .{e.withPath(entry.path)}); - Global.crash(); - } - - var outfile = try createFile(destination_dir_, entry.path, .{}); - defer outfile.close(); - - const stat = in_file.stat() catch continue; - _ = C.fchmod(outfile.handle, @intCast(stat.mode)); - - bun.copyFileWithState(in_file.handle, outfile.handle, ©_file_state).unwrap() catch |err| { - Output.prettyError("{s}: copying file {}", .{ @errorName(err), bun.fmt.fmtOSPath(entry.path, .{}) }); - Global.crash(); - }; - } - } - - return real_file_count; - } - }; - var pkg_in_cache_dir = try cache_dir.openDir(cache_dir_subpath, .{ .iterate = true }); defer pkg_in_cache_dir.close(); - var walker = Walker.walk(pkg_in_cache_dir, manager.allocator, &.{}, IGNORED_PATHS) catch bun.outOfMemory(); + var walker = Walker.walk(pkg_in_cache_dir, manager.allocator, &.{}, ignored_paths) catch bun.outOfMemory(); defer walker.deinit(); - var buf1: if (bun.Environment.isWindows) bun.WPathBuffer else void = undefined; - var buf2: if (bun.Environment.isWindows) bun.WPathBuffer else void = undefined; - var in_dir: if (bun.Environment.isWindows) []const u16 else void = undefined; - var out_dir: if (bun.Environment.isWindows) []const u16 else void = undefined; - if (comptime bun.Environment.isWindows) { + var buf1: bun.WPathBuffer = undefined; + var buf2: bun.WPathBuffer = undefined; + const inlen = bun.windows.kernel32.GetFinalPathNameByHandleW(pkg_in_cache_dir.fd, &buf1, buf1.len, 0); if (inlen == 0) { const e = bun.windows.Win32Error.get(); const err = if (e.toSystemErrno()) |sys_err| bun.errnoToZigErr(sys_err) else error.Unexpected; - Output.prettyError("error: copying file {}", .{err}); + manager.cleanErrorGeneric("copying file {}", .{err}); Global.crash(); } - in_dir = buf1[0..inlen]; - const outlen = bun.windows.kernel32.GetFinalPathNameByHandleW(node_modules_folder.fd, &buf2, buf2.len, 0); + const in_dir = buf1[0..inlen]; + const outlen = bun.windows.kernel32.GetFinalPathNameByHandleW(destination_dir.fd, &buf2, buf2.len, 0); if (outlen == 0) { const e = bun.windows.Win32Error.get(); const err = if (e.toSystemErrno()) |sys_err| bun.errnoToZigErr(sys_err) else error.Unexpected; - Output.prettyError("error: copying file {}", .{err}); + manager.cleanErrorGeneric("copying file {}", .{err}); Global.crash(); } - out_dir = buf2[0..outlen]; + const out_dir = buf2[0..outlen]; var tmpbuf: [1024]u8 = undefined; const tmpname = bun.span(bun.fs.FileSystem.instance.tmpname("tffbp", tmpbuf[0..], bun.fastRandom()) catch |e| { - Output.prettyError("error: copying file {s}", .{@errorName(e)}); + manager.cleanErrorGeneric("copying file {s}", .{@errorName(e)}); Global.crash(); }); - const temp_folder_in_node_modules = try node_modules_folder.makeOpenPath(tmpname, .{}); + const temp_folder_in_node_modules = try destination_dir.makeOpenPath(tmpname, .{}); defer { - node_modules_folder.deleteTree(tmpname) catch {}; + destination_dir.deleteTree(tmpname) catch {}; } - _ = try FileCopier.copy( - node_modules_folder, - &walker, - in_dir, - out_dir, - &buf1, - &buf2, - temp_folder_in_node_modules, - ); - } else if (Environment.isPosix) { - _ = try FileCopier.copy( - node_modules_folder, - &walker, - {}, - {}, - {}, - {}, - {}, - ); + + var real_file_count: u32 = 0; + + var copy_file_state: bun.CopyFileState = .{}; + var pathbuf: bun.PathBuffer = undefined; + var pathbuf2: bun.PathBuffer = undefined; + + while (try walker.next()) |entry| { + if (entry.kind != .file) continue; + real_file_count += 1; + + // 1. rename original file in node_modules to tmp_dir_in_node_modules + // 2. create the file again + // 3. copy cache flie to the newly re-created file + // 4. profit + var tmpbuf2: [1024]u8 = undefined; + const basename = bun.strings.fromWPath(pathbuf2[0..], entry.basename); + const tmpname2 = bun.span(bun.fs.FileSystem.instance.tmpname(basename, tmpbuf2[0..], bun.fastRandom()) catch |e| { + manager.cleanErrorGeneric("copying file {s}", .{@errorName(e)}); + Global.crash(); + }); + + const entrypath = bun.strings.fromWPath(pathbuf[0..], entry.path); + pathbuf[entrypath.len] = 0; + const entrypathZ = pathbuf[0..entrypath.len :0]; + + if (bun.sys.renameatConcurrently( + bun.toFD(destination_dir.fd), + entrypathZ, + bun.toFD(temp_folder_in_node_modules.fd), + tmpname2, + .{ .move_fallback = true }, + ).asErr()) |e| { + manager.cleanErrorGeneric("copying file {}", .{e}); + Global.crash(); + } + + var outfile = std.fs.Dir.createFile(destination_dir, entrypath, .{}) catch |e| { + manager.cleanErrorGeneric("failed to create file {s} ({s})", .{ entrypath, @errorName(e) }); + Global.crash(); + }; + outfile.close(); + + const infile_path = bun.path.joinStringBufWZ(buf1, &[_][]const u16{ in_dir, entry.path }, .auto); + const outfile_path = bun.path.joinStringBufWZ(buf2, &[_][]const u16{ out_dir, entry.path }, .auto); + + bun.copyFileWithState(infile_path, outfile_path, ©_file_state).unwrap() catch |err| { + manager.cleanError(err, "copying file {}", .{bun.fmt.fmtOSPath(entry.path, .{})}); + Global.crash(); + }; + } + return; + } + + var copy_file_state: bun.CopyFileState = .{}; + var pathbuf: bun.PathBuffer = undefined; + + while (try walker.next()) |entry| { + if (entry.kind != .file) continue; + + var in_file = try std.fs.Dir.openFile(entry.dir, entry.basename, .{ .mode = .read_only }); + defer in_file.close(); + + @memcpy(pathbuf[0..entry.path.len], entry.path); + pathbuf[entry.path.len] = 0; + + if (bun.sys.unlinkat( + bun.toFD(destination_dir.fd), + pathbuf[0..entry.path.len :0], + ).asErr()) |e| { + manager.cleanErrorGeneric("copying file {}", .{e.withPath(entry.path)}); + Global.crash(); + } + + var outfile = try std.fs.Dir.createFile(destination_dir, entry.path, .{}); + defer outfile.close(); + + const stat = in_file.stat() catch continue; + _ = C.fchmod(outfile.handle, @intCast(stat.mode)); + + bun.copyFileWithState(in_file.handle, outfile.handle, ©_file_state).unwrap() catch |err| { + manager.cleanError(err, "copying file {}", .{bun.fmt.fmtOSPath(entry.path, .{})}); + Global.crash(); + }; } } @@ -11354,16 +11407,16 @@ pub const PackageManager = struct { .err => |cause| { if (log_level != .silent) { switch (cause.step) { - .open_file => Output.prettyError("error opening lockfile: {s}\n", .{ + .open_file => manager.cleanErrorGeneric("failed to open lockfile: {s}", .{ @errorName(cause.value), }), - .parse_file => Output.prettyError("error parsing lockfile: {s}\n", .{ + .parse_file => manager.cleanErrorGeneric("failed to parse lockfile: {s}", .{ @errorName(cause.value), }), - .read_file => Output.prettyError("error reading lockfile: {s}\n", .{ + .read_file => manager.cleanErrorGeneric("failed to read lockfile: {s}", .{ @errorName(cause.value), }), - .migrating => Output.prettyError("error migrating lockfile: {s}\n", .{ + .migrating => manager.cleanErrorGeneric("failed to migrate lockfile: {s}", .{ @errorName(cause.value), }), } @@ -11402,8 +11455,8 @@ pub const PackageManager = struct { var root_node_modules = switch (bun.sys.openatOSPath(bun.FD.cwd(), bun.OSPathLiteral("node_modules"), bun.O.DIRECTORY | bun.O.RDONLY, 0o755)) { .result => |fd| std.fs.Dir{ .fd = fd.cast() }, .err => |e| { - Output.prettyError( - "error: failed to open root node_modules folder: {}\n", + manager.cleanErrorGeneric( + "failed to open root node_modules folder: {}", .{e}, ); Global.crash(); @@ -11421,8 +11474,8 @@ pub const PackageManager = struct { switch (bun.sys.File.toSource(package_json_path, manager.allocator)) { .result => |s| break :brk s, .err => |e| { - Output.prettyError( - "error: failed to read package.json: {}\n", + manager.cleanErrorGeneric( + "failed to read package.json: {}", .{e.withPath(package_json_path).toSystemError()}, ); Global.crash(); @@ -11433,6 +11486,7 @@ pub const PackageManager = struct { initializeStore(); const json = JSON.parsePackageJSONUTF8AlwaysDecode(&package_json_source, manager.log, manager.allocator) catch |err| { + manager.pauseProgress(); switch (Output.enable_ansi_colors) { inline else => |enable_ansi_colors| { manager.log.printForLogLevelWithEnableAnsiColors(Output.errorWriter(), enable_ansi_colors) catch {}; @@ -11446,8 +11500,8 @@ pub const PackageManager = struct { if (json.asProperty("version")) |v| { if (v.expr.asString(manager.allocator)) |s| break :version s; } - Output.prettyError( - "error: invalid package.json, missing or invalid property \"version\": {s}\n", + manager.cleanErrorGeneric( + "invalid package.json, missing or invalid property \"version\": {s}", .{package_json_source.path.text}, ); Global.crash(); @@ -11458,8 +11512,8 @@ pub const PackageManager = struct { const name = lockfile.str(&package.name); const actual_package = switch (lockfile.package_index.get(package.name_hash) orelse { - Output.prettyError( - "error: failed to find package in lockfile package index, this is a bug in Bun. Please file a GitHub issue.\n", + manager.cleanErrorGeneric( + "failed to find package in lockfile package index, this is a bug in Bun. Please file a GitHub issue.", .{}, ); Global.crash(); @@ -11473,7 +11527,7 @@ pub const PackageManager = struct { break :brk pkg; } } - Output.prettyError("error: could not find package with name: {s}\n", .{ + manager.cleanErrorGeneric("could not find package with name: {s}", .{ package.name.slice(lockfile.buffers.string_bytes.items), }); Global.crash(); @@ -11495,7 +11549,7 @@ pub const PackageManager = struct { }, .name_and_version => brk: { const name, const version = Dependency.splitNameAndVersion(argument); - const pkg_id, const node_modules = pkgInfoForNameAndVersion(lockfile, &iterator, argument, name, version); + const pkg_id, const node_modules = pkgInfoForNameAndVersion(manager, lockfile, &iterator, argument, name, version); const changes_dir = bun.path.joinZBuf(pathbuf[0..], &[_][]const u8{ node_modules.relative_path, @@ -11532,8 +11586,8 @@ pub const PackageManager = struct { const cache_dir_path = switch (bun.sys.getFdPath(bun.toFD(cache_dir.fd), &buf2)) { .result => |s| s, .err => |e| { - Output.prettyError( - "error: failed to read from cache {}\n", + manager.cleanErrorGeneric( + "failed to read from cache {}", .{e.toSystemError()}, ); Global.crash(); @@ -11546,8 +11600,8 @@ pub const PackageManager = struct { }; const random_tempdir = bun.span(bun.fs.FileSystem.instance.tmpname("node_modules_tmp", buf2[0..], bun.fastRandom()) catch |e| { - Output.prettyError( - "error: failed to make tempdir {s}\n", + manager.cleanErrorGeneric( + "failed to make tempdir {s}", .{@errorName(e)}, ); Global.crash(); @@ -11560,8 +11614,8 @@ pub const PackageManager = struct { // will `rename()` it out and back again. const has_nested_node_modules = has_nested_node_modules: { var new_folder_handle = std.fs.cwd().openDir(new_folder, .{}) catch |e| { - Output.prettyError( - "error: failed to open directory {s} {s}\n", + manager.cleanErrorGeneric( + "failed to open directory {s} {s}", .{ new_folder, @errorName(e) }, ); Global.crash(); @@ -11580,8 +11634,8 @@ pub const PackageManager = struct { }; const patch_tag_tmpname = bun.span(bun.fs.FileSystem.instance.tmpname("patch_tmp", buf3[0..], bun.fastRandom()) catch |e| { - Output.prettyError( - "error: failed to make tempdir {s}\n", + manager.cleanErrorGeneric( + "failed to make tempdir {s}", .{@errorName(e)}, ); Global.crash(); @@ -11601,8 +11655,8 @@ pub const PackageManager = struct { break :has_bun_patch_tag null; }; var new_folder_handle = std.fs.cwd().openDir(new_folder, .{}) catch |e| { - Output.prettyError( - "error: failed to open directory {s} {s}\n", + manager.cleanErrorGeneric( + "failed to open directory {s} {s}", .{ new_folder, @errorName(e) }, ); Global.crash(); @@ -11616,7 +11670,7 @@ pub const PackageManager = struct { patch_tag_tmpname, .{ .move_fallback = true }, ).asErr()) |e| { - Output.warn("failed renaming the bun patch tag, this may cause issues: {}", .{e}); + manager.cleanWarnResume("failed renaming the bun patch tag, this may cause issues: {}", .{e}); break :has_bun_patch_tag null; } break :has_bun_patch_tag patch_tag; @@ -11624,8 +11678,8 @@ pub const PackageManager = struct { defer { if (has_nested_node_modules or bun_patch_tag != null) { var new_folder_handle = std.fs.cwd().openDir(new_folder, .{}) catch |e| { - Output.prettyError( - "error: failed to open directory {s} {s}\n", + manager.cleanErrorGeneric( + "failed to open directory {s} {s}", .{ new_folder, @errorName(e) }, ); Global.crash(); @@ -11640,7 +11694,7 @@ pub const PackageManager = struct { "node_modules", .{ .move_fallback = true }, ).asErr()) |e| { - Output.warn("failed renaming nested node_modules folder, this may cause issues: {}", .{e}); + manager.cleanWarnResume("failed renaming nested node_modules folder, this may cause issues: {}", .{e}); } } @@ -11652,7 +11706,7 @@ pub const PackageManager = struct { patch_tag, .{ .move_fallback = true }, ).asErr()) |e| { - Output.warn("failed renaming the bun patch tag, this may cause issues: {}", .{e}); + manager.cleanWarnResume("failed renaming the bun patch tag, this may cause issues: {}", .{e}); } } } @@ -11662,8 +11716,8 @@ pub const PackageManager = struct { const cwd = switch (bun.sys.getcwdZ(&cwdbuf)) { .result => |fd| fd, .err => |e| { - Output.prettyError( - "error: failed to get cwd path {}\n", + manager.cleanErrorGeneric( + "failed to get cwd path {}", .{e}, ); Global.crash(); @@ -11671,8 +11725,8 @@ pub const PackageManager = struct { }; var gitbuf: bun.PathBuffer = undefined; const git = bun.which(&gitbuf, bun.getenvZ("PATH") orelse "", cwd, "git") orelse { - Output.prettyError( - "error: git must be installed to use `bun patch --commit` \n", + manager.cleanErrorGeneric( + "git must be installed to use `bun patch --commit`", .{}, ); Global.crash(); @@ -11681,16 +11735,16 @@ pub const PackageManager = struct { const opts = bun.patch.spawnOpts(paths[0], paths[1], cwd, git, &manager.event_loop); var spawn_result = switch (bun.spawnSync(&opts) catch |e| { - Output.prettyError( - "error: failed to make diff {s}\n", + manager.cleanErrorGeneric( + "failed to make diff {s}", .{@errorName(e)}, ); Global.crash(); }) { .result => |r| r, .err => |e| { - Output.prettyError( - "error: failed to make diff {}\n", + manager.cleanErrorGeneric( + "failed to make diff {}", .{e}, ); Global.crash(); @@ -11698,8 +11752,8 @@ pub const PackageManager = struct { }; const contents = switch (bun.patch.diffPostProcess(&spawn_result, paths[0], paths[1]) catch |e| { - Output.prettyError( - "error: failed to make diff {s}\n", + manager.cleanErrorGeneric( + "failed to make diff {s}", .{@errorName(e)}, ); Global.crash(); @@ -11722,8 +11776,8 @@ pub const PackageManager = struct { } else try writer.print("{s}", .{this.stderr.items[0..]}); } }; - Output.prettyError( - "error: failed to make diff {}\n", + manager.cleanErrorGeneric( + "failed to make diff {}", .{ Truncate{ .stderr = stderr }, }, @@ -12683,6 +12737,8 @@ pub const PackageManager = struct { .symlink => { const directory = this.manager.globalLinkDir() catch |err| { if (comptime log_level != .silent) { + this.manager.pauseProgress(); + defer this.manager.resumeProgress(); const fmt = "\nerror: unable to access global directory while installing {s}: {s}\n"; const args = .{ name, @errorName(err) }; @@ -12861,7 +12917,7 @@ pub const PackageManager = struct { // creating this directory now, right before installing package var destination_dir = this.node_modules.makeAndOpenDir(this.root_node_modules_folder) catch |err| { if (log_level != .silent) { - Output.err(err, "Failed to open node_modules folder for {s} in {s}", .{ + this.manager.cleanErrorResume(err, "Failed to open node_modules folder for {s} in {s}", .{ name, bun.fmt.fmtPath(u8, this.node_modules.path.items, .{}), }); @@ -12945,6 +13001,8 @@ pub const PackageManager = struct { const count = this.getInstalledPackageScriptsCount(alias, package_id, resolution.tag, destination_dir, log_level); if (count > 0) { if (comptime log_level.isVerbose()) { + this.manager.pauseProgress(); + defer this.manager.resumeProgress(); Output.prettyError("Blocked {d} scripts for: {s}@{}\n", .{ count, alias, @@ -12970,8 +13028,8 @@ pub const PackageManager = struct { if (!pkg_has_patch) this.incrementTreeInstallCount(this.current_tree_id, destination_dir, !is_pending_package_install, log_level); if (cause.err == error.DanglingSymlink) { - Output.prettyErrorln( - "error: {s} \"link:{s}\" not found (try running 'bun link' in the intended package's folder)", + this.manager.cleanErrorGenericResume( + "{s} \"link:{s}\" not found (try running 'bun link' in the intended package's folder)", .{ @errorName(cause.err), this.names[package_id].slice(buf) }, ); this.summary.fail += 1; @@ -12987,7 +13045,7 @@ pub const PackageManager = struct { if (!Singleton.node_modules_is_ok) { if (!Environment.isWindows) { const stat = bun.sys.fstat(bun.toFD(destination_dir)).unwrap() catch |err| { - Output.err("EACCES", "Permission denied while installing {s}", .{ + this.manager.cleanError("EACCES", "Permission denied while installing {s}", .{ this.names[package_id].slice(buf), }); if (Environment.isDebug) { @@ -13004,21 +13062,21 @@ pub const PackageManager = struct { stat.mode & bun.S.IWOTH > 0; if (!is_writable) { - Output.err("EACCES", "Permission denied while writing packages into node_modules.", .{}); + this.manager.cleanError("EACCES", "Permission denied while writing packages into node_modules.", .{}); Global.exit(1); } } Singleton.node_modules_is_ok = true; } - Output.err("EACCES", "Permission denied while installing {s}", .{ + this.manager.cleanErrorResume("EACCES", "Permission denied while installing {s}", .{ this.names[package_id].slice(buf), }); this.summary.fail += 1; } else { - Output.prettyErrorln( - "error: {s} installing {s} ({s})", + this.manager.cleanErrorGenericResume( + "{s} installing {s} ({s})", .{ @errorName(cause.err), this.names[package_id].slice(buf), install_result.fail.step.name() }, ); this.summary.fail += 1; @@ -13032,7 +13090,7 @@ pub const PackageManager = struct { var destination_dir = this.node_modules.makeAndOpenDir(this.root_node_modules_folder) catch |err| { if (log_level != .silent) { - Output.err(err, "Failed to open node_modules folder for {s} in {s}", .{ + this.manager.cleanError(err, "Failed to open node_modules folder for {s} in {s}", .{ name, bun.fmt.fmtPath(u8, this.node_modules.path.items, .{}), }); @@ -13103,6 +13161,8 @@ pub const PackageManager = struct { resolution, ) catch |err| { if (comptime log_level != .silent) { + this.manager.pauseProgress(); + defer this.manager.resumeProgress(); const fmt = "\nerror: failed to enqueue lifecycle scripts for {s}: {s}\n"; const args = .{ folder_name, @errorName(err) }; @@ -13173,6 +13233,8 @@ pub const PackageManager = struct { this.node.completeOne(); } if (comptime log_level.isVerbose()) { + this.manager.pauseProgress(); + defer this.manager.resumeProgress(); const name = this.lockfile.str(&this.names[package_id]); if (!meta.os.isMatch() and !meta.arch.isMatch()) { Output.prettyErrorln("Skip installing '{s}' cpu & os mismatch", .{name}); @@ -13440,7 +13502,7 @@ pub const PackageManager = struct { } }; break :brk bun.openDir(cwd, "node_modules") catch |err| { - Output.prettyErrorln("error: {s} opening node_modules folder", .{@errorName(err)}); + this.cleanErrorGeneric("{s} opening node_modules folder", .{@errorName(err)}); Global.crash(); }; }; @@ -13693,6 +13755,8 @@ pub const PackageManager = struct { } if (PackageManager.verbose_install and PackageManager.instance.pendingTaskCount() > 0) { + closure.manager.pauseProgress(); + defer closure.manager.resumeProgress(); if (PackageManager.hasEnoughTimePassedBetweenWaitingMessages()) Output.prettyErrorln("[PackageManager] waiting for {d} tasks\n", .{PackageManager.instance.pendingTaskCount()}); } @@ -13741,6 +13805,8 @@ pub const PackageManager = struct { while (this.pending_lifecycle_script_tasks.load(.monotonic) > 0) { if (PackageManager.verbose_install) { + this.pauseProgress(); + defer this.resumeProgress(); if (PackageManager.hasEnoughTimePassedBetweenWaitingMessages()) Output.prettyErrorln("[PackageManager] waiting for {d} scripts\n", .{this.pending_lifecycle_script_tasks.load(.monotonic)}); } @@ -13876,7 +13942,6 @@ pub const PackageManager = struct { // this defaults to false // but we force allowing updates to the lockfile when you do bun add var had_any_diffs = false; - manager.progress = .{}; // Step 2. Parse the package.json file const root_package_json_source = logger.Source.initPathString(package_json_cwd, root_package_json_contents); @@ -13885,16 +13950,16 @@ pub const PackageManager = struct { .err => |cause| { if (log_level != .silent) { switch (cause.step) { - .open_file => Output.prettyError("error opening lockfile: {s}\n", .{ + .open_file => Output.errGeneric("failed to open lockfile: {s}", .{ @errorName(cause.value), }), - .parse_file => Output.prettyError("error parsing lockfile: {s}\n", .{ + .parse_file => Output.errGeneric("failed to parse lockfile: {s}", .{ @errorName(cause.value), }), - .read_file => Output.prettyError("error reading lockfile: {s}\n", .{ + .read_file => Output.errGeneric("failed to read lockfile: {s}", .{ @errorName(cause.value), }), - .migrating => Output.prettyError("error migrating lockfile: {s}\n", .{ + .migrating => Output.errGeneric("failed to migrate lockfile: {s}", .{ @errorName(cause.value), }), } @@ -14198,7 +14263,7 @@ pub const PackageManager = struct { if (manager.options.enable.frozen_lockfile and load_lockfile_result != .not_found) { if (comptime log_level != .silent) { - Output.prettyErrorln("error: lockfile had changes, but lockfile is frozen", .{}); + manager.cleanErrorGeneric("lockfile had changes, but lockfile is frozen", .{}); } Global.crash(); } @@ -14293,6 +14358,8 @@ pub const PackageManager = struct { const pending_tasks = this.pendingTaskCount(); if (PackageManager.verbose_install and pending_tasks > 0) { + this.pauseProgress(); + defer this.resumeProgress(); if (PackageManager.hasEnoughTimePassedBetweenWaitingMessages()) Output.prettyErrorln("[PackageManager] waiting for {d} tasks\n", .{pending_tasks}); } @@ -14339,8 +14406,10 @@ pub const PackageManager = struct { } const had_errors_before_cleaning_lockfile = manager.log.hasErrors(); + manager.pauseProgress(); try manager.log.printForLogLevel(Output.errorWriter()); manager.log.reset(); + manager.resumeProgress(); // This operation doesn't perform any I/O, so it should be relatively cheap. manager.lockfile = try manager.lockfile.cleanWithLogger( @@ -14438,6 +14507,8 @@ pub const PackageManager = struct { if (manager.options.enable.frozen_lockfile and load_lockfile_result != .not_found) { if (manager.lockfile.hasMetaHashChanged(PackageManager.verbose_install or manager.options.do.print_meta_hash_string, packages_len_before_install) catch false) { if (comptime log_level != .silent) { + manager.pauseProgress(); + defer manager.resumeProgress(); Output.prettyErrorln("error: lockfile had changes, but lockfile is frozen", .{}); Output.note("try re-running without --frozen-lockfile and commit the updated lockfile", .{}); } @@ -14456,6 +14527,8 @@ pub const PackageManager = struct { } if (comptime log_level != .silent) { + manager.pauseProgress(); + defer manager.resumeProgress(); try manager.log.printForLogLevel(Output.errorWriter()); } if (had_errors_before_cleaning_lockfile or manager.log.hasErrors()) Global.crash(); @@ -14489,12 +14562,18 @@ pub const PackageManager = struct { break :brk; } - if (log_level != .silent) Output.prettyErrorln("\nerror: {s} deleting empty lockfile", .{@errorName(err)}); + if (log_level != .silent) { + manager.pauseProgress(); + defer manager.resumeProgress(); + Output.prettyErrorln("\nerror: {s} deleting empty lockfile", .{@errorName(err)}); + } break :save; }; } if (!manager.options.global) { if (log_level != .silent) { + manager.pauseProgress(); + defer manager.resumeProgress(); switch (manager.subcommand) { .remove => Output.prettyErrorln("\npackage.json has no dependencies! Deleted empty lockfile", .{}), else => Output.prettyErrorln("No packages! Deleted empty lockfile", .{}), @@ -14575,7 +14654,11 @@ pub const PackageManager = struct { while (manager.pending_lifecycle_script_tasks.load(.monotonic) > 0) { if (PackageManager.verbose_install) { - if (PackageManager.hasEnoughTimePassedBetweenWaitingMessages()) Output.prettyErrorln("[PackageManager] waiting for {d} scripts\n", .{manager.pending_lifecycle_script_tasks.load(.monotonic)}); + if (PackageManager.hasEnoughTimePassedBetweenWaitingMessages()) { + manager.pauseProgress(); + defer manager.resumeProgress(); + Output.prettyErrorln("[PackageManager] waiting for {d} scripts\n", .{manager.pending_lifecycle_script_tasks.load(.monotonic)}); + } } manager.sleep(); @@ -14732,6 +14815,8 @@ pub const PackageManager = struct { this.options.remote_package_features, )) continue; if (log_level != .silent) { + this.pauseProgress(); + defer this.resumeProgress(); if (failed_dep.name.isEmpty() or strings.eqlLong(failed_dep.name.slice(string_buf), failed_dep.version.literal.slice(string_buf), true)) { Output.errGeneric("{} failed to resolve", .{ failed_dep.version.literal.fmt(string_buf), diff --git a/src/install/lifecycle_script_runner.zig b/src/install/lifecycle_script_runner.zig index f9df8bfd639f2..652a0ef353c94 100644 --- a/src/install/lifecycle_script_runner.zig +++ b/src/install/lifecycle_script_runner.zig @@ -437,6 +437,8 @@ pub const LifecycleScriptSubprocess = struct { }); if (comptime log_level.isVerbose()) { + manager.pauseProgress(); + defer manager.resumeProgress(); Output.prettyErrorln("[Scripts] Starting scripts for \"{s}\"", .{ list.package_name, }); @@ -445,6 +447,8 @@ pub const LifecycleScriptSubprocess = struct { _ = manager.pending_lifecycle_script_tasks.fetchAdd(1, .monotonic); lifecycle_subprocess.spawnNextScript(list.first_index) catch |err| { + manager.pauseProgress(); + defer manager.resumeProgress(); Output.prettyErrorln("error: Failed to run script {s} due to error {s}", .{ Lockfile.Scripts.names[list.first_index], @errorName(err), From 82b65981a2d5780bad0bf286ec8990d8f5030b28 Mon Sep 17 00:00:00 2001 From: Dylan Conway Date: Tue, 8 Oct 2024 19:29:19 -0700 Subject: [PATCH 07/19] fix build --- src/install/install.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/install/install.zig b/src/install/install.zig index 0179ec3e7a912..875175a6cd243 100644 --- a/src/install/install.zig +++ b/src/install/install.zig @@ -11336,8 +11336,8 @@ pub const PackageManager = struct { }; outfile.close(); - const infile_path = bun.path.joinStringBufWZ(buf1, &[_][]const u16{ in_dir, entry.path }, .auto); - const outfile_path = bun.path.joinStringBufWZ(buf2, &[_][]const u16{ out_dir, entry.path }, .auto); + const infile_path = bun.path.joinStringBufWZ(&buf1, &[_][]const u16{ in_dir, entry.path }, .auto); + const outfile_path = bun.path.joinStringBufWZ(&buf2, &[_][]const u16{ out_dir, entry.path }, .auto); bun.copyFileWithState(infile_path, outfile_path, ©_file_state).unwrap() catch |err| { manager.cleanError(err, "copying file {}", .{bun.fmt.fmtOSPath(entry.path, .{})}); From bfa2a2bc99bbdaf69facfeae76a6b6ab24460e1b Mon Sep 17 00:00:00 2001 From: Dylan Conway Date: Tue, 8 Oct 2024 19:31:56 -0700 Subject: [PATCH 08/19] undo --- src/install/install.zig | 230 +++++++++++++++++++++++----------------- 1 file changed, 131 insertions(+), 99 deletions(-) diff --git a/src/install/install.zig b/src/install/install.zig index 875175a6cd243..ee96f8931ff61 100644 --- a/src/install/install.zig +++ b/src/install/install.zig @@ -11250,133 +11250,165 @@ pub const PackageManager = struct { cache_dir_subpath: []const u8, node_modules_folder_path: []const u8, ) !void { - var destination_dir = try std.fs.cwd().openDir(node_modules_folder_path, .{ .iterate = true }); - defer destination_dir.close(); + var node_modules_folder = try std.fs.cwd().openDir(node_modules_folder_path, .{ .iterate = true }); + defer node_modules_folder.close(); - const ignored_paths: []const bun.OSPathSlice = &[_][]const bun.OSPathChar{ + const IGNORED_PATHS: []const bun.OSPathSlice = &[_][]const bun.OSPathChar{ bun.OSPathLiteral("node_modules"), bun.OSPathLiteral(".git"), bun.OSPathLiteral("CMakeFiles"), }; + const FileCopier = struct { + pub fn copy( + destination_dir_: std.fs.Dir, + walker: *Walker, + in_dir: if (bun.Environment.isWindows) []const u16 else void, + out_dir: if (bun.Environment.isWindows) []const u16 else void, + buf1: if (bun.Environment.isWindows) []u16 else void, + buf2: if (bun.Environment.isWindows) []u16 else void, + tmpdir_in_node_modules: if (bun.Environment.isWindows) std.fs.Dir else void, + ) !u32 { + var real_file_count: u32 = 0; + + var copy_file_state: bun.CopyFileState = .{}; + var pathbuf: bun.PathBuffer = undefined; + var pathbuf2: bun.PathBuffer = undefined; + // _ = pathbuf; // autofix + + while (try walker.next()) |entry| { + if (entry.kind != .file) continue; + real_file_count += 1; + const openFile = std.fs.Dir.openFile; + const createFile = std.fs.Dir.createFile; + + // 1. rename original file in node_modules to tmp_dir_in_node_modules + // 2. create the file again + // 3. copy cache flie to the newly re-created file + // 4. profit + if (comptime bun.Environment.isWindows) { + var tmpbuf: [1024]u8 = undefined; + const basename = bun.strings.fromWPath(pathbuf2[0..], entry.basename); + const tmpname = bun.span(bun.fs.FileSystem.instance.tmpname(basename, tmpbuf[0..], bun.fastRandom()) catch |e| { + Output.prettyError("error: copying file {s}", .{@errorName(e)}); + Global.crash(); + }); + + const entrypath = bun.strings.fromWPath(pathbuf[0..], entry.path); + pathbuf[entrypath.len] = 0; + const entrypathZ = pathbuf[0..entrypath.len :0]; + + if (bun.sys.renameatConcurrently( + bun.toFD(destination_dir_.fd), + entrypathZ, + bun.toFD(tmpdir_in_node_modules.fd), + tmpname, + .{ .move_fallback = true }, + ).asErr()) |e| { + Output.prettyError("error: copying file {}", .{e}); + Global.crash(); + } + + var outfile = createFile(destination_dir_, entrypath, .{}) catch |e| { + Output.prettyError("error: failed to create file {s} ({s})", .{ entrypath, @errorName(e) }); + Global.crash(); + }; + outfile.close(); + + const infile_path = bun.path.joinStringBufWZ(buf1, &[_][]const u16{ in_dir, entry.path }, .auto); + const outfile_path = bun.path.joinStringBufWZ(buf2, &[_][]const u16{ out_dir, entry.path }, .auto); + + bun.copyFileWithState(infile_path, outfile_path, ©_file_state).unwrap() catch |err| { + Output.prettyError("{s}: copying file {}", .{ @errorName(err), bun.fmt.fmtOSPath(entry.path, .{}) }); + Global.crash(); + }; + } else if (comptime Environment.isPosix) { + var in_file = try openFile(entry.dir, entry.basename, .{ .mode = .read_only }); + defer in_file.close(); + + @memcpy(pathbuf[0..entry.path.len], entry.path); + pathbuf[entry.path.len] = 0; + + if (bun.sys.unlinkat( + bun.toFD(destination_dir_.fd), + pathbuf[0..entry.path.len :0], + ).asErr()) |e| { + Output.prettyError("error: copying file {}", .{e.withPath(entry.path)}); + Global.crash(); + } + + var outfile = try createFile(destination_dir_, entry.path, .{}); + defer outfile.close(); + + const stat = in_file.stat() catch continue; + _ = C.fchmod(outfile.handle, @intCast(stat.mode)); + + bun.copyFileWithState(in_file.handle, outfile.handle, ©_file_state).unwrap() catch |err| { + Output.prettyError("{s}: copying file {}", .{ @errorName(err), bun.fmt.fmtOSPath(entry.path, .{}) }); + Global.crash(); + }; + } + } + + return real_file_count; + } + }; + var pkg_in_cache_dir = try cache_dir.openDir(cache_dir_subpath, .{ .iterate = true }); defer pkg_in_cache_dir.close(); - var walker = Walker.walk(pkg_in_cache_dir, manager.allocator, &.{}, ignored_paths) catch bun.outOfMemory(); + var walker = Walker.walk(pkg_in_cache_dir, manager.allocator, &.{}, IGNORED_PATHS) catch bun.outOfMemory(); defer walker.deinit(); - if (comptime bun.Environment.isWindows) { - var buf1: bun.WPathBuffer = undefined; - var buf2: bun.WPathBuffer = undefined; + var buf1: if (bun.Environment.isWindows) bun.WPathBuffer else void = undefined; + var buf2: if (bun.Environment.isWindows) bun.WPathBuffer else void = undefined; + var in_dir: if (bun.Environment.isWindows) []const u16 else void = undefined; + var out_dir: if (bun.Environment.isWindows) []const u16 else void = undefined; + if (comptime bun.Environment.isWindows) { const inlen = bun.windows.kernel32.GetFinalPathNameByHandleW(pkg_in_cache_dir.fd, &buf1, buf1.len, 0); if (inlen == 0) { const e = bun.windows.Win32Error.get(); const err = if (e.toSystemErrno()) |sys_err| bun.errnoToZigErr(sys_err) else error.Unexpected; - manager.cleanErrorGeneric("copying file {}", .{err}); + Output.prettyError("error: copying file {}", .{err}); Global.crash(); } - const in_dir = buf1[0..inlen]; - const outlen = bun.windows.kernel32.GetFinalPathNameByHandleW(destination_dir.fd, &buf2, buf2.len, 0); + in_dir = buf1[0..inlen]; + const outlen = bun.windows.kernel32.GetFinalPathNameByHandleW(node_modules_folder.fd, &buf2, buf2.len, 0); if (outlen == 0) { const e = bun.windows.Win32Error.get(); const err = if (e.toSystemErrno()) |sys_err| bun.errnoToZigErr(sys_err) else error.Unexpected; - manager.cleanErrorGeneric("copying file {}", .{err}); + Output.prettyError("error: copying file {}", .{err}); Global.crash(); } - const out_dir = buf2[0..outlen]; + out_dir = buf2[0..outlen]; var tmpbuf: [1024]u8 = undefined; const tmpname = bun.span(bun.fs.FileSystem.instance.tmpname("tffbp", tmpbuf[0..], bun.fastRandom()) catch |e| { - manager.cleanErrorGeneric("copying file {s}", .{@errorName(e)}); + Output.prettyError("error: copying file {s}", .{@errorName(e)}); Global.crash(); }); - const temp_folder_in_node_modules = try destination_dir.makeOpenPath(tmpname, .{}); + const temp_folder_in_node_modules = try node_modules_folder.makeOpenPath(tmpname, .{}); defer { - destination_dir.deleteTree(tmpname) catch {}; + node_modules_folder.deleteTree(tmpname) catch {}; } - - var real_file_count: u32 = 0; - - var copy_file_state: bun.CopyFileState = .{}; - var pathbuf: bun.PathBuffer = undefined; - var pathbuf2: bun.PathBuffer = undefined; - - while (try walker.next()) |entry| { - if (entry.kind != .file) continue; - real_file_count += 1; - - // 1. rename original file in node_modules to tmp_dir_in_node_modules - // 2. create the file again - // 3. copy cache flie to the newly re-created file - // 4. profit - var tmpbuf2: [1024]u8 = undefined; - const basename = bun.strings.fromWPath(pathbuf2[0..], entry.basename); - const tmpname2 = bun.span(bun.fs.FileSystem.instance.tmpname(basename, tmpbuf2[0..], bun.fastRandom()) catch |e| { - manager.cleanErrorGeneric("copying file {s}", .{@errorName(e)}); - Global.crash(); - }); - - const entrypath = bun.strings.fromWPath(pathbuf[0..], entry.path); - pathbuf[entrypath.len] = 0; - const entrypathZ = pathbuf[0..entrypath.len :0]; - - if (bun.sys.renameatConcurrently( - bun.toFD(destination_dir.fd), - entrypathZ, - bun.toFD(temp_folder_in_node_modules.fd), - tmpname2, - .{ .move_fallback = true }, - ).asErr()) |e| { - manager.cleanErrorGeneric("copying file {}", .{e}); - Global.crash(); - } - - var outfile = std.fs.Dir.createFile(destination_dir, entrypath, .{}) catch |e| { - manager.cleanErrorGeneric("failed to create file {s} ({s})", .{ entrypath, @errorName(e) }); - Global.crash(); - }; - outfile.close(); - - const infile_path = bun.path.joinStringBufWZ(&buf1, &[_][]const u16{ in_dir, entry.path }, .auto); - const outfile_path = bun.path.joinStringBufWZ(&buf2, &[_][]const u16{ out_dir, entry.path }, .auto); - - bun.copyFileWithState(infile_path, outfile_path, ©_file_state).unwrap() catch |err| { - manager.cleanError(err, "copying file {}", .{bun.fmt.fmtOSPath(entry.path, .{})}); - Global.crash(); - }; - } - return; - } - - var copy_file_state: bun.CopyFileState = .{}; - var pathbuf: bun.PathBuffer = undefined; - - while (try walker.next()) |entry| { - if (entry.kind != .file) continue; - - var in_file = try std.fs.Dir.openFile(entry.dir, entry.basename, .{ .mode = .read_only }); - defer in_file.close(); - - @memcpy(pathbuf[0..entry.path.len], entry.path); - pathbuf[entry.path.len] = 0; - - if (bun.sys.unlinkat( - bun.toFD(destination_dir.fd), - pathbuf[0..entry.path.len :0], - ).asErr()) |e| { - manager.cleanErrorGeneric("copying file {}", .{e.withPath(entry.path)}); - Global.crash(); - } - - var outfile = try std.fs.Dir.createFile(destination_dir, entry.path, .{}); - defer outfile.close(); - - const stat = in_file.stat() catch continue; - _ = C.fchmod(outfile.handle, @intCast(stat.mode)); - - bun.copyFileWithState(in_file.handle, outfile.handle, ©_file_state).unwrap() catch |err| { - manager.cleanError(err, "copying file {}", .{bun.fmt.fmtOSPath(entry.path, .{})}); - Global.crash(); - }; + _ = try FileCopier.copy( + node_modules_folder, + &walker, + in_dir, + out_dir, + &buf1, + &buf2, + temp_folder_in_node_modules, + ); + } else if (Environment.isPosix) { + _ = try FileCopier.copy( + node_modules_folder, + &walker, + {}, + {}, + {}, + {}, + {}, + ); } } From 589b657bb4062c4c1e7388745695b60912376faf Mon Sep 17 00:00:00 2001 From: Dylan Conway Date: Tue, 8 Oct 2024 20:00:13 -0700 Subject: [PATCH 09/19] fail tests --- src/install/install.zig | 8 +- .../registry/bun-install-registry.test.ts | 121 ++++++++++++++++++ 2 files changed, 125 insertions(+), 4 deletions(-) diff --git a/src/install/install.zig b/src/install/install.zig index ee96f8931ff61..691f6f732fee7 100644 --- a/src/install/install.zig +++ b/src/install/install.zig @@ -8419,16 +8419,16 @@ pub const PackageManager = struct { switch (err) { error.LoadCAFile => { if (!bun.sys.existsZ(opts.abs_ca_file_name)) { - PackageManager.instance.cleanError("HTTPThread", "failed to find CA file: '{s}'", .{opts.abs_ca_file_name}); + PackageManager.instance.cleanError("HTTPThread", "could not find CA file: '{s}'", .{opts.abs_ca_file_name}); } else { - PackageManager.instance.cleanError("HTTPThread", "failed to load CA file: '{s}'", .{opts.abs_ca_file_name}); + PackageManager.instance.cleanError("HTTPThread", "invalid CA file: '{s}'", .{opts.abs_ca_file_name}); } }, error.InvalidCAFile => { - PackageManager.instance.cleanError("HTTPThread", "the CA file is invalid: '{s}'", .{opts.abs_ca_file_name}); + PackageManager.instance.cleanError("HTTPThread", "invalid CA file: '{s}'", .{opts.abs_ca_file_name}); }, error.InvalidCA => { - PackageManager.instance.cleanError("HTTPThread", "the provided CA is invalid", .{}); + PackageManager.instance.cleanError("HTTPThread", "the CA is invalid", .{}); }, error.FailedToOpenSocket => { PackageManager.instance.cleanErrorGeneric("failed to start HTTP client thread", .{}); diff --git a/test/cli/install/registry/bun-install-registry.test.ts b/test/cli/install/registry/bun-install-registry.test.ts index bd1e915a12b98..0362d5570c40f 100644 --- a/test/cli/install/registry/bun-install-registry.test.ts +++ b/test/cli/install/registry/bun-install-registry.test.ts @@ -514,6 +514,127 @@ ${Object.keys(opts) ); }); +describe("certificate authority", () => { + test(`non-existent --cafile`, async () => { + await write( + join(packageDir, "package.json"), + JSON.stringify({ name: "foo", version: "1.0.0", "dependencies": { "no-deps": "1.1.1" } }), + ); + + const { stdout, stderr, exited } = spawn({ + cmd: [bunExe(), "install", "--cafile", "does-not-exist"], + cwd: packageDir, + stderr: "pipe", + stdout: "pipe", + env, + }); + const out = await Bun.readableStreamToText(stdout); + expect(out).not.toContain("no-deps"); + const err = await Bun.readableStreamToText(stderr); + expect(err).toContain(`HTTPThread: could not find CA file: '${join(packageDir, "does-not-exist")}'`); + expect(await exited).toBe(1); + }); + + test("cafile from bunfig does not exist", async () => { + await Promise.all([ + write( + join(packageDir, "package.json"), + JSON.stringify({ + name: "foo", + version: "1.0.0", + dependencies: { + "no-deps": "1.1.1", + }, + }), + ), + write( + join(packageDir, "bunfig.toml"), + ` + [install] + cache = false + registry = "http://localhost:${port}/" + cafile = "does-not-exist"`, + ), + ]); + + const { stdout, stderr, exited } = spawn({ + cmd: [bunExe(), "install"], + cwd: packageDir, + stderr: "pipe", + stdout: "pipe", + env, + }); + + const out = await Bun.readableStreamToText(stdout); + expect(out).not.toContain("no-deps"); + const err = await Bun.readableStreamToText(stderr); + expect(err).toContain(`HTTPThread: could not find CA file: '${join(packageDir, "does-not-exist")}'`); + expect(await exited).toBe(1); + }); + test("invalid cafile", async () => { + await Promise.all([ + write( + join(packageDir, "package.json"), + JSON.stringify({ + name: "foo", + version: "1.0.0", + dependencies: { + "no-deps": "1.1.1", + }, + }), + ), + write( + join(packageDir, "invalid-cafile"), + `-----BEGIN CERTIFICATE----- +jlwkjekfjwlejlgldjfljlkwjef +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +ljelkjwelkgjw;lekj;lkejflkj +-----END CERTIFICATE-----`, + ), + ]); + + const { stdout, stderr, exited } = spawn({ + cmd: [bunExe(), "install", "--cafile", join(packageDir, "invalid-cafile")], + cwd: packageDir, + stderr: "pipe", + stdout: "pipe", + env, + }); + + const out = await Bun.readableStreamToText(stdout); + expect(out).not.toContain("no-deps"); + const err = await Bun.readableStreamToText(stderr); + expect(err).toContain(`HTTPThread: invalid CA file: '${join(packageDir, "invalid-cafile")}'`); + expect(await exited).toBe(1); + }); + test("invalid --ca", async () => { + await write( + join(packageDir, "package.json"), + JSON.stringify({ + name: "foo", + version: "1.0.0", + dependencies: { + "no-deps": "1.1.1", + }, + }), + ); + const { stdout, stderr, exited } = spawn({ + cmd: [bunExe(), "install", "--ca", "not-valid"], + cwd: packageDir, + stderr: "pipe", + stdout: "pipe", + env, + }); + + const out = await Bun.readableStreamToText(stdout); + expect(out).not.toContain("no-deps"); + const err = await Bun.readableStreamToText(stderr); + expect(err).toContain("HTTPThread: the CA is invalid"); + expect(await exited).toBe(1); + }); +}); + export async function publish( env: any, cwd: string, From c2eb8d2318b09bbf7a57e1f4713164d1752a7fab Mon Sep 17 00:00:00 2001 From: Dylan Conway Date: Wed, 9 Oct 2024 16:24:10 -0700 Subject: [PATCH 10/19] todo --- src/http.zig | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/http.zig b/src/http.zig index 075c1d2dc0ace..85ad2418a9298 100644 --- a/src/http.zig +++ b/src/http.zig @@ -1153,7 +1153,14 @@ pub const HTTPThread = struct { requested_config.deinit(); bun.default_allocator.destroy(requested_config); bun.default_allocator.destroy(custom_context); - return err; + + // TODO: these error names reach js. figure out how they should be handled + return switch (err) { + error.FailedToOpenSocket => |e| e, + error.InvalidCA => error.FailedToOpenSocket, + error.InvalidCAFile => error.FailedToOpenSocket, + error.LoadCAFile => error.FailedToOpenSocket, + }; }; try custom_ssl_context_map.put(requested_config, custom_context); // We might deinit the socket context, so we disable keepalive to make sure we don't From 10e4d4f7027ecd0978f97ce5772dc0de4b091542 Mon Sep 17 00:00:00 2001 From: Dylan Conway Date: Wed, 9 Oct 2024 17:11:15 -0700 Subject: [PATCH 11/19] success tests --- .../registry/bun-install-registry.test.ts | 107 ++++++++++++++++++ 1 file changed, 107 insertions(+) diff --git a/test/cli/install/registry/bun-install-registry.test.ts b/test/cli/install/registry/bun-install-registry.test.ts index 0362d5570c40f..5519da539ed62 100644 --- a/test/cli/install/registry/bun-install-registry.test.ts +++ b/test/cli/install/registry/bun-install-registry.test.ts @@ -22,6 +22,7 @@ import { toMatchNodeModulesAt, writeShebangScript, stderrForInstall, + tls, } from "harness"; import { join, resolve, sep } from "path"; import { readdirSorted } from "../dummy.registry"; @@ -515,6 +516,112 @@ ${Object.keys(opts) }); describe("certificate authority", () => { + const mockRegistryFetch = function (opts?: any): (req: Request) => Promise { + return async function (req: Request) { + // console.log({ url: req.url }); + if (req.url.includes("no-deps")) { + return new Response(Bun.file(join(import.meta.dir, "packages", "no-deps", "package.json"))); + } + return new Response("OK", { status: 200 }); + }; + }; + test("valid --cafile", async () => { + console.log({ packageDir }); + using server = Bun.serve({ + port: 0, + fetch: mockRegistryFetch(), + ...tls, + }); + await Promise.all([ + write( + join(packageDir, "package.json"), + JSON.stringify({ + name: "foo", + version: "1.1.1", + dependencies: { + "no-deps": "1.0.0", + }, + }), + ), + write( + join(packageDir, "bunfig.toml"), + ` + [install] + cache = false + registry = "https://localhost:${server.port}/"`, + ), + write(join(packageDir, "cafile"), tls.cert), + ]); + + const { stdout, stderr, exited } = spawn({ + cmd: [bunExe(), "install", "--cafile", "cafile"], + cwd: packageDir, + stderr: "pipe", + stdout: "pipe", + env, + }); + const out = await Bun.readableStreamToText(stdout); + expect(out).toContain("+ no-deps@1.0.0"); + const err = await Bun.readableStreamToText(stderr); + expect(err).not.toContain("ConnectionClosed"); + expect(err).not.toContain("error:"); + expect(err).not.toContain("DEPTH_ZERO_SELF_SIGNED_CERT"); + expect(await exited).toBe(0); + }); + test("valid --ca", async () => { + using server = Bun.serve({ + port: 0, + fetch: mockRegistryFetch(), + ...tls, + }); + await Promise.all([ + write( + join(packageDir, "package.json"), + JSON.stringify({ + name: "foo", + version: "1.1.1", + dependencies: { + "no-deps": "1.0.0", + }, + }), + ), + write( + join(packageDir, "bunfig.toml"), + ` + [install] + cache = false + registry = "https://localhost:${server.port}/"`, + ), + ]); + + // first without ca, should fail + let { stdout, stderr, exited } = spawn({ + cmd: [bunExe(), "install"], + cwd: packageDir, + stderr: "pipe", + stdout: "pipe", + env, + }); + let out = await Bun.readableStreamToText(stdout); + let err = await Bun.readableStreamToText(stderr); + expect(err).toContain("DEPTH_ZERO_SELF_SIGNED_CERT"); + expect(await exited).toBe(1); + + // now with a valid ca + ({ stdout, stderr, exited } = spawn({ + cmd: [bunExe(), "install", "--ca", tls.cert], + cwd: packageDir, + stderr: "pipe", + stdout: "pipe", + env, + })); + out = await Bun.readableStreamToText(stdout); + expect(out).toContain("+ no-deps@1.0.0"); + err = await Bun.readableStreamToText(stderr); + expect(err).not.toContain("DEPTH_ZERO_SELF_SIGNED_CERT"); + expect(err).not.toContain("error:"); + expect(await exited).toBe(0); + }); test(`non-existent --cafile`, async () => { await write( join(packageDir, "package.json"), From a79e982544dc45c7187bcfd637ba59181067ff2f Mon Sep 17 00:00:00 2001 From: Dylan Conway Date: Wed, 9 Oct 2024 17:33:52 -0700 Subject: [PATCH 12/19] bunfig docs --- docs/runtime/bunfig.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/runtime/bunfig.md b/docs/runtime/bunfig.md index 4af518744556a..2ec3689e46cfe 100644 --- a/docs/runtime/bunfig.md +++ b/docs/runtime/bunfig.md @@ -370,6 +370,19 @@ myorg = { username = "myusername", password = "$npm_password", url = "https://re myorg = { token = "$npm_token", url = "https://registry.myorg.com/" } ``` +### `install.ca` and `install.cafile` + +To configure a CA certificate, use `install.ca` or `install.cafile` to specify a path to a CA certificate file. + +```toml +[install] +# the CA certificate as a string +ca = "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----" + +# a path to a CA certificate +cafile = "path/to/cafile" +``` + ### `install.cache` To configure the cache behavior: From d3866bb422a28180c379000e8fd51ba2fefb1580 Mon Sep 17 00:00:00 2001 From: Dylan Conway Date: Wed, 9 Oct 2024 17:43:39 -0700 Subject: [PATCH 13/19] multiple --- docs/runtime/bunfig.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/runtime/bunfig.md b/docs/runtime/bunfig.md index 2ec3689e46cfe..1bfcd540e5bb8 100644 --- a/docs/runtime/bunfig.md +++ b/docs/runtime/bunfig.md @@ -376,10 +376,10 @@ To configure a CA certificate, use `install.ca` or `install.cafile` to specify a ```toml [install] -# the CA certificate as a string +# The CA certificate as a string ca = "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----" -# a path to a CA certificate +# A path to a CA certificate file. The file can contain multiple certificates. cafile = "path/to/cafile" ``` From e0c84370d95040ff59e32bf91b0dc5ff8cf012ff Mon Sep 17 00:00:00 2001 From: Dylan Conway Date: Wed, 9 Oct 2024 18:00:55 -0700 Subject: [PATCH 14/19] remove todo comment --- src/http.zig | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/http.zig b/src/http.zig index 85ad2418a9298..dc1f6628e4886 100644 --- a/src/http.zig +++ b/src/http.zig @@ -637,8 +637,6 @@ fn NewHTTPContext(comptime ssl: bool) type { .ca = if (init_opts.ca.len > 0) @ptrCast(init_opts.ca) else null, .ca_count = @intCast(init_opts.ca.len), .ca_file_name = if (init_opts.abs_ca_file_name.len > 0) init_opts.abs_ca_file_name else null, - - // TODO: is this needed? .request_cert = 1, }; From e79efbc224a6190704fcbac722ce357739f4febd Mon Sep 17 00:00:00 2001 From: Dylan Conway Date: Wed, 9 Oct 2024 22:26:06 -0700 Subject: [PATCH 15/19] fix test --- .../install/registry/bun-install-registry.test.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/test/cli/install/registry/bun-install-registry.test.ts b/test/cli/install/registry/bun-install-registry.test.ts index 5519da539ed62..7c2d32126ef7e 100644 --- a/test/cli/install/registry/bun-install-registry.test.ts +++ b/test/cli/install/registry/bun-install-registry.test.ts @@ -518,15 +518,13 @@ ${Object.keys(opts) describe("certificate authority", () => { const mockRegistryFetch = function (opts?: any): (req: Request) => Promise { return async function (req: Request) { - // console.log({ url: req.url }); if (req.url.includes("no-deps")) { - return new Response(Bun.file(join(import.meta.dir, "packages", "no-deps", "package.json"))); + return new Response(Bun.file(join(import.meta.dir, "packages", "no-deps", "no-deps-1.0.0.tgz"))); } return new Response("OK", { status: 200 }); }; }; test("valid --cafile", async () => { - console.log({ packageDir }); using server = Bun.serve({ port: 0, fetch: mockRegistryFetch(), @@ -539,7 +537,7 @@ describe("certificate authority", () => { name: "foo", version: "1.1.1", dependencies: { - "no-deps": "1.0.0", + "no-deps": `https://localhost:${server.port}/no-deps-1.0.0.tgz`, }, }), ), @@ -561,7 +559,7 @@ describe("certificate authority", () => { env, }); const out = await Bun.readableStreamToText(stdout); - expect(out).toContain("+ no-deps@1.0.0"); + expect(out).toContain("+ no-deps@"); const err = await Bun.readableStreamToText(stderr); expect(err).not.toContain("ConnectionClosed"); expect(err).not.toContain("error:"); @@ -581,7 +579,7 @@ describe("certificate authority", () => { name: "foo", version: "1.1.1", dependencies: { - "no-deps": "1.0.0", + "no-deps": `https://localhost:${server.port}/no-deps-1.0.0.tgz`, }, }), ), @@ -616,7 +614,7 @@ describe("certificate authority", () => { env, })); out = await Bun.readableStreamToText(stdout); - expect(out).toContain("+ no-deps@1.0.0"); + expect(out).toContain("+ no-deps@"); err = await Bun.readableStreamToText(stderr); expect(err).not.toContain("DEPTH_ZERO_SELF_SIGNED_CERT"); expect(err).not.toContain("error:"); From 7f2629117938c3bb6b5a76ab406fe39b9cb57725 Mon Sep 17 00:00:00 2001 From: Dylan Conway Date: Thu, 10 Oct 2024 15:59:34 -0700 Subject: [PATCH 16/19] Revert "cleanup output" This reverts commit abc43544dda862eae6e61729e91b99a8d7c70533. --- src/Progress.zig | 4 +- src/cli/pack_command.zig | 2 +- src/cli/package_manager_command.zig | 2 +- src/cli/publish_command.zig | 4 +- src/http.zig | 62 +++-- src/install/bin.zig | 1 + src/install/install.zig | 303 ++++++++---------------- src/install/lifecycle_script_runner.zig | 4 - 8 files changed, 139 insertions(+), 243 deletions(-) diff --git a/src/Progress.zig b/src/Progress.zig index d8cad42ba3f38..822feb7576b5c 100644 --- a/src/Progress.zig +++ b/src/Progress.zig @@ -380,11 +380,11 @@ pub fn lock_stderr(p: *Progress) void { p.terminal = null; }; } - std.debug.lockStdErr(); + std.debug.getStderrMutex().lock(); } pub fn unlock_stderr(p: *Progress) void { - std.debug.unlockStdErr(); + std.debug.getStderrMutex().unlock(); p.update_mutex.unlock(); } diff --git a/src/cli/pack_command.zig b/src/cli/pack_command.zig index b6a1b87b39989..4d4bc36b40590 100644 --- a/src/cli/pack_command.zig +++ b/src/cli/pack_command.zig @@ -1959,7 +1959,7 @@ pub const PackCommand = struct { } // only produce this error only when we need to get the workspace version - Output.errGeneric("Failed to resolve workspace version for \"{s}\" in `{s}`. Run `bun install` and try again.", .{ + Output.errGeneric("Failed to resolve workspace version for \"{s}\" in `{s}`. Run `bun install` and try again.", .{ dependency_name, dependency_group, }); diff --git a/src/cli/package_manager_command.zig b/src/cli/package_manager_command.zig index c0fd615f4501e..801f4929360d0 100644 --- a/src/cli/package_manager_command.zig +++ b/src/cli/package_manager_command.zig @@ -159,7 +159,7 @@ pub const PackageManagerCommand = struct { switch (err) { error.OutOfMemory => bun.outOfMemory(), error.NeedAuth => { - Output.errGeneric("missing authentication (run `bunx npm login`)", .{}); + Output.errGeneric("missing authentication (run `bunx npm login`)", .{}); }, error.ProbablyInvalidAuth => { Output.errGeneric("failed to authenticate with registry '{}'", .{ diff --git a/src/cli/publish_command.zig b/src/cli/publish_command.zig index 694d7f15688e0..c54903373a09d 100644 --- a/src/cli/publish_command.zig +++ b/src/cli/publish_command.zig @@ -372,7 +372,7 @@ pub const PublishCommand = struct { switch (err) { error.OutOfMemory => bun.outOfMemory(), error.NeedAuth => { - Output.errGeneric("missing authentication (run `bunx npm login`)", .{}); + Output.errGeneric("missing authentication (run `bunx npm login`)", .{}); Global.crash(); }, } @@ -419,7 +419,7 @@ pub const PublishCommand = struct { switch (err) { error.OutOfMemory => bun.outOfMemory(), error.NeedAuth => { - Output.errGeneric("missing authentication (run `bunx npm login`)", .{}); + Output.errGeneric("missing authentication (run `bunx npm login`)", .{}); Global.crash(); }, } diff --git a/src/http.zig b/src/http.zig index dc1f6628e4886..424adab91d3ea 100644 --- a/src/http.zig +++ b/src/http.zig @@ -34,7 +34,6 @@ const Progress = bun.Progress; const X509 = @import("./bun.js/api/bun/x509.zig"); const SSLConfig = @import("./bun.js/api/server.zig").ServerConfig.SSLConfig; const SSLWrapper = @import("./bun.js/api/bun/ssl_wrapper.zig").SSLWrapper; -const PackageManager = bun.install.PackageManager; const URLBufferPool = ObjectPool([8192]u8, null, false, 10); const uws = bun.uws; @@ -517,13 +516,6 @@ pub const HTTPCertError = struct { reason: [:0]const u8 = "", }; -pub const InitError = error{ - FailedToOpenSocket, - LoadCAFile, - InvalidCAFile, - InvalidCA, -}; - fn NewHTTPContext(comptime ssl: bool) type { return struct { const pool_size = 64; @@ -593,6 +585,13 @@ fn NewHTTPContext(comptime ssl: bool) type { bun.default_allocator.destroy(this); } + pub const InitError = error{ + FailedToOpenSocket, + LoadCAFile, + InvalidCAFile, + InvalidCA, + }; + pub fn initWithClientConfig(this: *@This(), client: *HTTPClient) InitError!void { if (!comptime ssl) { @compileError("ssl only"); @@ -1042,34 +1041,9 @@ pub const HTTPThread = struct { return this.lazy_libdeflater.?; } - fn onInitErrorNoop(err: InitError, opts: InitOpts) noreturn { - switch (err) { - error.LoadCAFile => { - if (!bun.sys.existsZ(opts.abs_ca_file_name)) { - Output.err("HTTPThread", "failed to find CA file: '{s}'", .{opts.abs_ca_file_name}); - } else { - Output.err("HTTPThread", "failed to load CA file: '{s}'", .{opts.abs_ca_file_name}); - } - }, - error.InvalidCAFile => { - Output.err("HTTPThread", "the CA file is invalid: '{s}'", .{opts.abs_ca_file_name}); - }, - error.InvalidCA => { - Output.err("HTTPThread", "the provided CA is invalid", .{}); - }, - error.FailedToOpenSocket => { - Output.errGeneric("failed to start HTTP client thread", .{}); - }, - } - Global.crash(); - } - pub const InitOpts = struct { ca: []stringZ = &.{}, abs_ca_file_name: stringZ = &.{}, - for_install: bool = false, - - onInitError: *const fn (err: InitError, opts: InitOpts) noreturn = &onInitErrorNoop, }; fn initOnce(opts: *const InitOpts) void { @@ -1114,7 +1088,27 @@ pub const HTTPThread = struct { http_thread.loop = loop; http_thread.http_context.init(); - http_thread.https_context.initWithThreadOpts(&opts) catch |err| opts.onInitError(err, opts); + http_thread.https_context.initWithThreadOpts(&opts) catch |err| { + switch (err) { + error.LoadCAFile => { + if (!bun.sys.existsZ(opts.abs_ca_file_name)) { + Output.err("HTTPThread", "failed to find CA file: '{s}'", .{opts.abs_ca_file_name}); + } else { + Output.err("HTTPThread", "failed to load CA file: '{s}'", .{opts.abs_ca_file_name}); + } + }, + error.InvalidCAFile => { + Output.err("HTTPThread", "the CA file is invalid: '{s}'", .{opts.abs_ca_file_name}); + }, + error.InvalidCA => { + Output.err("HTTPThread", "the provided CA is invalid", .{}); + }, + error.FailedToOpenSocket => { + Output.errGeneric("failed to start HTTP client thread", .{}); + }, + } + Global.crash(); + }; http_thread.has_awoken.store(true, .monotonic); http_thread.processEvents(); } diff --git a/src/install/bin.zig b/src/install/bin.zig index dc792ac944111..8361b90bc9106 100644 --- a/src/install/bin.zig +++ b/src/install/bin.zig @@ -2,6 +2,7 @@ const ExternalStringList = @import("./install.zig").ExternalStringList; const Semver = @import("./semver.zig"); const ExternalString = Semver.ExternalString; const String = Semver.String; +const Output = bun.Output; const Global = bun.Global; const std = @import("std"); const strings = bun.strings; diff --git a/src/install/install.zig b/src/install/install.zig index 691f6f732fee7..1204add2b267e 100644 --- a/src/install/install.zig +++ b/src/install/install.zig @@ -677,8 +677,6 @@ pub const Task = struct { pt.apply() catch bun.outOfMemory(); if (pt.callback.apply.logger.errors > 0) { defer pt.callback.apply.logger.deinit(); - manager.pauseProgress(); - defer manager.resumeProgress(); // this.log.addErrorFmt(null, logger.Loc.Empty, bun.default_allocator, "failed to apply patch: {}", .{e}) catch unreachable; pt.callback.apply.logger.printForLogLevel(Output.writer()) catch {}; } @@ -1626,9 +1624,9 @@ pub fn NewPackageInstall(comptime kind: PkgInstallKind) type { } if (bun.windows.Win32Error.get().toSystemErrno()) |err| { - PackageManager.instance.cleanError(err, "copying file {}", .{bun.fmt.fmtOSPath(entry.path, .{})}); + Output.prettyError("{s}: copying file {}", .{ @tagName(err), bun.fmt.fmtOSPath(entry.path, .{}) }); } else { - PackageManager.instance.cleanErrorGeneric("copying file {}", .{bun.fmt.fmtOSPath(entry.path, .{})}); + Output.prettyError("error copying file {}", .{bun.fmt.fmtOSPath(entry.path, .{})}); } Global.crash(); @@ -1656,7 +1654,7 @@ pub fn NewPackageInstall(comptime kind: PkgInstallKind) type { progress_.refresh(); } - PackageManager.instance.cleanError(err, "copying file {}", .{bun.fmt.fmtOSPath(entry.path, .{})}); + Output.prettyErrorln("{s}: copying file {}", .{ @errorName(err), bun.fmt.fmtOSPath(entry.path, .{}) }); Global.crash(); }; }; @@ -1673,7 +1671,7 @@ pub fn NewPackageInstall(comptime kind: PkgInstallKind) type { progress_.refresh(); } - PackageManager.instance.cleanError(err, "copying file {}", .{bun.fmt.fmtOSPath(entry.path, .{})}); + Output.prettyError("{s}: copying file {}", .{ @errorName(err), bun.fmt.fmtOSPath(entry.path, .{}) }); Global.crash(); }; } @@ -2041,7 +2039,7 @@ pub fn NewPackageInstall(comptime kind: PkgInstallKind) type { }.get(); if (once_log) { - PackageManager.instance.cleanWarnResume("CreateHardLinkW failed, falling back to CopyFileW: {} -> {}\n", .{ + Output.warn("CreateHardLinkW failed, falling back to CopyFileW: {} -> {}\n", .{ bun.fmt.fmtOSPath(src, .{}), bun.fmt.fmtOSPath(dest, .{}), }); @@ -6341,12 +6339,10 @@ pub const PackageManager = struct { } if (comptime log_level.isVerbose()) { - manager.pauseProgress(); Output.prettyError(" ", .{}); Output.printElapsed(@as(f64, @floatFromInt(task.http.elapsed)) / std.time.ns_per_ms); Output.prettyError("\nDownloaded {s} versions\n", .{name.slice()}); Output.flush(); - manager.resumeProgress(); } if (response.status_code == 304) { @@ -6546,12 +6542,10 @@ pub const PackageManager = struct { } if (comptime log_level.isVerbose()) { - manager.pauseProgress(); Output.prettyError(" ", .{}); Output.printElapsed(@as(f64, @floatCast(@as(f64, @floatFromInt(task.http.elapsed)) / std.time.ns_per_ms))); Output.prettyError(" Downloaded {s} tarball\n", .{extract.name.slice()}); Output.flush(); - manager.resumeProgress(); } if (comptime log_level.showProgress()) { @@ -6577,9 +6571,7 @@ pub const PackageManager = struct { if (task.log.msgs.items.len > 0) { switch (Output.enable_ansi_colors) { inline else => |enable_ansi_colors| { - manager.pauseProgress(); try task.log.printForLogLevelWithEnableAnsiColors(Output.errorWriter(), enable_ansi_colors); - manager.resumeProgress(); }, } } @@ -6903,59 +6895,6 @@ pub const PackageManager = struct { } } - pub inline fn pauseProgress(this: *PackageManager) void { - if (this.options.log_level.showProgress() and Output.enable_ansi_colors) { - this.progress.lock_stderr(); - } - } - - pub inline fn resumeProgress(this: *PackageManager) void { - if (this.options.log_level.showProgress() and Output.enable_ansi_colors) { - this.progress.unlock_stderr(); - } - } - - /// Will pause and clear progrss from terminal, print the error, then resume progress. Best for errors that - /// are not immediately fatal. - pub inline fn cleanErrorGenericResume(this: *PackageManager, comptime fmt: string, args: anytype) void { - this.pauseProgress(); - Output.errGeneric(fmt, args); - this.resumeProgress(); - } - - /// Will pause and clear progrss from terminal, print the error, then resume progress. Best for errors that - /// are not immediately fatal. - pub inline fn cleanErrorResume(this: *PackageManager, name: anytype, comptime fmt: string, args: anytype) void { - this.pauseProgress(); - Output.err(name, fmt, args); - this.resumeProgress(); - } - - /// Will pause and clear progress from terminal if it exists. Does not resume progress, best for calling - /// before `Global.crash()`. - pub inline fn cleanErrorGeneric(this: *PackageManager, comptime fmt: string, args: anytype) void { - this.pauseProgress(); - Output.errGeneric(fmt, args); - } - - /// Will pause and clear progress from terminal if it exists. Does not resume progress, best for calling - /// before `Global.crash()`. - pub inline fn cleanError(this: *PackageManager, name: anytype, comptime fmt: string, args: anytype) void { - this.pauseProgress(); - Output.err(name, fmt, args); - } - - pub inline fn cleanWarnResume(this: *PackageManager, comptime fmt: string, args: anytype) void { - this.pauseProgress(); - Output.warn(fmt, args); - this.resumeProgress(); - } - - pub inline fn cleanWarn(this: *PackageManager, comptime fmt: string, args: anytype) void { - this.pauseProgress(); - Output.warn(fmt, args); - } - pub const Options = struct { log_level: LogLevel = .default, global: bool = false, @@ -8743,6 +8682,7 @@ pub const PackageManager = struct { .resolve_tasks = .{}, .lockfile = undefined, .root_package_json_file = root_package_json_file, + // .progress .event_loop = .{ .mini = JSC.MiniEventLoop.init(bun.default_allocator), }, @@ -8808,7 +8748,6 @@ pub const PackageManager = struct { HTTP.HTTPThread.init(&.{ .ca = ca, .abs_ca_file_name = abs_ca_file_name, - .onInitError = &httpThreadOnInitError, }); manager.timestamp_for_manifest_cache_control = brk: { @@ -8884,7 +8823,7 @@ pub const PackageManager = struct { }; manager.lockfile = try allocator.create(Lockfile); - if (Output.enable_ansi_colors) { + if (Output.enable_ansi_colors_stderr) { manager.progress = Progress{}; manager.progress.supports_ansi_escape_codes = Output.enable_ansi_colors_stderr; manager.root_progress_node = manager.progress.start("", 0); @@ -9048,7 +8987,7 @@ pub const PackageManager = struct { // Step 1. parse the nearest package.json file { const package_json_source = bun.sys.File.toSource(manager.original_package_json_path, ctx.allocator).unwrap() catch |err| { - manager.cleanErrorGeneric("failed to read \"{s}\" for linking: {s}", .{ manager.original_package_json_path, @errorName(err) }); + Output.errGeneric("failed to read \"{s}\" for linking: {s}", .{ manager.original_package_json_path, @errorName(err) }); Global.crash(); }; lockfile.initEmpty(ctx.allocator); @@ -9150,7 +9089,7 @@ pub const PackageManager = struct { .node_modules = bun.toFD(node_modules.fd), .node_modules_path = bun.getFdPath(node_modules, &node_modules_path_buf) catch |err| { if (manager.options.log_level != .silent) { - manager.cleanError(err, "failed to link binary", .{}); + Output.err(err, "failed to link binary", .{}); } Global.crash(); }, @@ -9231,7 +9170,7 @@ pub const PackageManager = struct { // Step 1. parse the nearest package.json file { const package_json_source = bun.sys.File.toSource(manager.original_package_json_path, ctx.allocator).unwrap() catch |err| { - manager.cleanErrorGeneric("failed to read \"{s}\" for unlinking: {s}", .{ manager.original_package_json_path, @errorName(err) }); + Output.errGeneric("failed to read \"{s}\" for unlinking: {s}", .{ manager.original_package_json_path, @errorName(err) }); Global.crash(); }; lockfile.initEmpty(ctx.allocator); @@ -9240,12 +9179,12 @@ pub const PackageManager = struct { name = lockfile.str(&package.name); if (name.len == 0) { if (manager.options.log_level != .silent) { - manager.cleanErrorGeneric("package.json missing \"name\" in \"{s}\"", .{package_json_source.path.text}); + Output.prettyErrorln("error: package.json missing \"name\" in \"{s}\"", .{package_json_source.path.text}); } Global.crash(); } else if (!strings.isNPMPackageName(name)) { if (manager.options.log_level != .silent) { - manager.cleanErrorGeneric("invalid package.json name \"{s}\" in \"{s}\"", .{ + Output.prettyErrorln("error: invalid package.json name \"{s}\" in \"{s}\"", .{ name, package_json_source.path.text, }); @@ -9280,7 +9219,7 @@ pub const PackageManager = struct { break :brk manager.global_dir.?.makeOpenPath("node_modules", .{}) catch |err| { if (manager.options.log_level != .silent) - manager.cleanErrorGeneric("failed to create node_modules in global dir due to error {s}", .{@errorName(err)}); + Output.prettyErrorln("error: failed to create node_modules in global dir due to error {s}", .{@errorName(err)}); Global.crash(); }; }; @@ -9317,14 +9256,14 @@ pub const PackageManager = struct { // delete it if it exists node_modules.deleteTree(name) catch |err| { if (manager.options.log_level != .silent) - manager.cleanErrorGeneric("failed to unlink package in global dir due to error {s}", .{@errorName(err)}); + Output.prettyErrorln("error: failed to unlink package in global dir due to error {s}", .{@errorName(err)}); Global.crash(); }; Output.prettyln("success: unlinked package \"{s}\"", .{name}); Global.exit(0); } else { - manager.cleanErrorGeneric("bun unlink {{packageName}} not implemented yet", .{}); + Output.prettyln("error: bun unlink {{packageName}} not implemented yet", .{}); Global.crash(); } } @@ -10406,14 +10345,14 @@ pub const PackageManager = struct { if (manager.options.positionals.len <= 1) { switch (manager.subcommand) { .add => { - manager.cleanErrorGeneric("no package specified to add", .{}); + Output.errGeneric("no package specified to add", .{}); Output.flush(); PackageManager.CommandLineArguments.printHelp(.add); Global.exit(0); }, .remove => { - manager.cleanErrorGeneric("no package specified to remove", .{}); + Output.errGeneric("no package specified to remove", .{}); Output.flush(); PackageManager.CommandLineArguments.printHelp(.remove); @@ -10468,14 +10407,14 @@ pub const PackageManager = struct { manager.log.printForLogLevelWithEnableAnsiColors(Output.errorWriter(), enable_ansi_colors) catch {}; }, } - manager.cleanErrorGeneric("failed to parse package.json \"{s}\": {s}", .{ + Output.errGeneric("failed to parse package.json \"{s}\": {s}", .{ manager.original_package_json_path, @errorName(err), }); Global.crash(); }, .read_err => |err| { - manager.cleanErrorGeneric("failed to read package.json \"{s}\": {s}", .{ + Output.errGeneric("failed to read package.json \"{s}\": {s}", .{ manager.original_package_json_path, @errorName(err), }); @@ -10493,10 +10432,10 @@ pub const PackageManager = struct { if (subcommand == .remove) { if (current_package_json.root.data != .e_object) { - manager.cleanErrorGeneric("package.json is not an Object {{}}, so there's nothing to {s}!", .{@tagName(subcommand)}); + Output.errGeneric("package.json is not an Object {{}}, so there's nothing to {s}!", .{@tagName(subcommand)}); Global.crash(); } else if (current_package_json.root.data.e_object.properties.len == 0) { - manager.cleanErrorGeneric("package.json is empty {{}}, so there's nothing to {s}!", .{@tagName(subcommand)}); + Output.errGeneric("package.json is empty {{}}, so there's nothing to {s}!", .{@tagName(subcommand)}); Global.crash(); } else if (current_package_json.root.asProperty("devDependencies") == null and current_package_json.root.asProperty("dependencies") == null and @@ -10629,7 +10568,7 @@ pub const PackageManager = struct { .indent = current_package_json_indent, }, ) catch |err| { - manager.cleanErrorGeneric("package.json failed to write due to error {s}", .{@errorName(err)}); + Output.prettyErrorln("package.json failed to write due to error {s}", .{@errorName(err)}); Global.crash(); }; @@ -10672,14 +10611,14 @@ pub const PackageManager = struct { manager.log.printForLogLevelWithEnableAnsiColors(Output.errorWriter(), enable_ansi_colors) catch {}; }, } - manager.cleanErrorGeneric("failed to parse package.json \"{s}\": {s}", .{ + Output.errGeneric("failed to parse package.json \"{s}\": {s}", .{ root_package_json_path, @errorName(err), }); Global.crash(); }, .read_err => |err| { - manager.cleanErrorGeneric("failed to read package.json \"{s}\": {s}", .{ + Output.errGeneric("failed to read package.json \"{s}\": {s}", .{ manager.original_package_json_path, @errorName(err), }); @@ -10709,7 +10648,7 @@ pub const PackageManager = struct { .indent = root_package_json.indentation, }, ) catch |err| { - manager.cleanErrorGeneric("package.json failed to write due to error {s}", .{@errorName(err)}); + Output.prettyErrorln("package.json failed to write due to error {s}", .{@errorName(err)}); Global.crash(); }; root_package_json.source.contents = try manager.allocator.dupe(u8, package_json_writer2.ctx.writtenWithoutTrailingZero()); @@ -10733,7 +10672,7 @@ pub const PackageManager = struct { // Now, we _re_ parse our in-memory edited package.json // so we can commit the version we changed from the lockfile var new_package_json = JSON.parsePackageJSONUTF8(&source, manager.log, manager.allocator) catch |err| { - manager.cleanErrorGeneric("package.json failed to parse due to error {s}", .{@errorName(err)}); + Output.prettyErrorln("package.json failed to parse due to error {s}", .{@errorName(err)}); Global.crash(); }; @@ -10772,7 +10711,7 @@ pub const PackageManager = struct { .indent = current_package_json_indent, }, ) catch |err| { - manager.cleanErrorGeneric("package.json failed to write due to error {s}", .{@errorName(err)}); + Output.prettyErrorln("package.json failed to write due to error {s}", .{@errorName(err)}); Global.crash(); }; @@ -10849,7 +10788,7 @@ pub const PackageManager = struct { } } else |err| { if (err != error.ENOENT) { - manager.cleanError(err, "while reading node_modules/.bin", .{}); + Output.err(err, "while reading node_modules/.bin", .{}); Global.crash(); } } @@ -10879,7 +10818,6 @@ pub const PackageManager = struct { const IdPair = struct { DependencyID, PackageID }; fn pkgInfoForNameAndVersion( - this: *PackageManager, lockfile: *Lockfile, iterator: *Lockfile.Tree.Iterator, pkg_maybe_version_to_patch: []const u8, @@ -10913,7 +10851,7 @@ pub const PackageManager = struct { } if (pairs.items.len == 0) { - this.cleanErrorGeneric("package {s} not found", .{pkg_maybe_version_to_patch}); + Output.prettyErrorln("\nerror: package {s} not found", .{pkg_maybe_version_to_patch}); Global.crash(); return; } @@ -10923,8 +10861,8 @@ pub const PackageManager = struct { if (pairs.items.len == 1) { const dep_id, const pkg_id = pairs.items[0]; const folder = (try nodeModulesFolderForDependencyID(iterator, dep_id)) orelse { - this.cleanErrorGeneric( - "could not find the folder for {s} in node_modules", + Output.prettyError( + "error: could not find the folder for {s} in node_modules\n", .{pkg_maybe_version_to_patch}, ); Global.crash(); @@ -10940,8 +10878,8 @@ pub const PackageManager = struct { // so we are going to try looking for each dep id in node_modules _, const pkg_id = pairs.items[0]; const folder = (try nodeModulesFolderForDependencyIDs(iterator, pairs.items)) orelse { - this.cleanErrorGeneric( - "could not find the folder for {s} in node_modules", + Output.prettyError( + "error: could not find the folder for {s} in node_modules\n", .{pkg_maybe_version_to_patch}, ); Global.crash(); @@ -10959,8 +10897,8 @@ pub const PackageManager = struct { if (pairs.items.len == 1) { const dep_id, const pkg_id = pairs.items[0]; const folder = (try nodeModulesFolderForDependencyID(iterator, dep_id)) orelse { - this.cleanErrorGeneric( - "could not find the folder for {s} in node_modules", + Output.prettyError( + "error: could not find the folder for {s} in node_modules\n", .{pkg_maybe_version_to_patch}, ); Global.crash(); @@ -10990,8 +10928,8 @@ pub const PackageManager = struct { if (count == pairs.items.len) { // It may be hoisted, so we'll try the first one that matches const folder = (try nodeModulesFolderForDependencyIDs(iterator, pairs.items)) orelse { - this.cleanErrorGeneric( - "could not find the folder for {s} in node_modules", + Output.prettyError( + "error: could not find the folder for {s} in node_modules\n", .{pkg_maybe_version_to_patch}, ); Global.crash(); @@ -11088,8 +11026,8 @@ pub const PackageManager = struct { switch (bun.sys.File.toSource(package_json_path, manager.allocator)) { .result => |s| break :src s, .err => |e| { - manager.cleanErrorGeneric( - "failed to read package.json: {}", + Output.prettyError( + "error: failed to read package.json: {}\n", .{e.withPath(package_json_path).toSystemError()}, ); Global.crash(); @@ -11100,7 +11038,6 @@ pub const PackageManager = struct { initializeStore(); const json = JSON.parsePackageJSONUTF8AlwaysDecode(&package_json_source, manager.log, manager.allocator) catch |err| { - manager.pauseProgress(); switch (Output.enable_ansi_colors) { inline else => |enable_ansi_colors| { manager.log.printForLogLevelWithEnableAnsiColors(Output.errorWriter(), enable_ansi_colors) catch {}; @@ -11114,8 +11051,8 @@ pub const PackageManager = struct { if (json.asProperty("version")) |v| { if (v.expr.asString(manager.allocator)) |s| break :version s; } - manager.cleanErrorGeneric( - "invalid package.json, missing or invalid property \"version\": {s}", + Output.prettyError( + "error: invalid package.json, missing or invalid property \"version\": {s}\n", .{package_json_source.path.text}, ); Global.crash(); @@ -11141,7 +11078,7 @@ pub const PackageManager = struct { break :id pkg; } } - manager.cleanErrorGeneric("could not find package with name: {s}", .{ + Output.prettyError("error: could not find package with name: {s}\n", .{ package.name.slice(lockfile.buffers.string_bytes.items), }); Global.crash(); @@ -11181,7 +11118,7 @@ pub const PackageManager = struct { .name_and_version => brk: { const pkg_maybe_version_to_patch = argument; const name, const version = Dependency.splitNameAndVersion(pkg_maybe_version_to_patch); - const pkg_id, const folder = pkgInfoForNameAndVersion(manager, manager.lockfile, &iterator, pkg_maybe_version_to_patch, name, version); + const pkg_id, const folder = pkgInfoForNameAndVersion(manager.lockfile, &iterator, pkg_maybe_version_to_patch, name, version); const pkg = manager.lockfile.packages.get(pkg_id); const pkg_name = pkg.name.slice(strbuf); @@ -11225,8 +11162,8 @@ pub const PackageManager = struct { // // So we will overwrite the folder by directly copying the package in cache into it manager.overwritePackageInNodeModulesFolder(cache_dir, cache_dir_subpath, module_folder) catch |e| { - manager.cleanErrorGeneric( - "error overwriting folder in node_modules: {s}", + Output.prettyError( + "error: error overwriting folder in node_modules: {s}\n", .{@errorName(e)}, ); Global.crash(); @@ -11439,16 +11376,16 @@ pub const PackageManager = struct { .err => |cause| { if (log_level != .silent) { switch (cause.step) { - .open_file => manager.cleanErrorGeneric("failed to open lockfile: {s}", .{ + .open_file => Output.prettyError("error opening lockfile: {s}\n", .{ @errorName(cause.value), }), - .parse_file => manager.cleanErrorGeneric("failed to parse lockfile: {s}", .{ + .parse_file => Output.prettyError("error parsing lockfile: {s}\n", .{ @errorName(cause.value), }), - .read_file => manager.cleanErrorGeneric("failed to read lockfile: {s}", .{ + .read_file => Output.prettyError("error reading lockfile: {s}\n", .{ @errorName(cause.value), }), - .migrating => manager.cleanErrorGeneric("failed to migrate lockfile: {s}", .{ + .migrating => Output.prettyError("error migrating lockfile: {s}\n", .{ @errorName(cause.value), }), } @@ -11487,8 +11424,8 @@ pub const PackageManager = struct { var root_node_modules = switch (bun.sys.openatOSPath(bun.FD.cwd(), bun.OSPathLiteral("node_modules"), bun.O.DIRECTORY | bun.O.RDONLY, 0o755)) { .result => |fd| std.fs.Dir{ .fd = fd.cast() }, .err => |e| { - manager.cleanErrorGeneric( - "failed to open root node_modules folder: {}", + Output.prettyError( + "error: failed to open root node_modules folder: {}\n", .{e}, ); Global.crash(); @@ -11506,8 +11443,8 @@ pub const PackageManager = struct { switch (bun.sys.File.toSource(package_json_path, manager.allocator)) { .result => |s| break :brk s, .err => |e| { - manager.cleanErrorGeneric( - "failed to read package.json: {}", + Output.prettyError( + "error: failed to read package.json: {}\n", .{e.withPath(package_json_path).toSystemError()}, ); Global.crash(); @@ -11518,7 +11455,6 @@ pub const PackageManager = struct { initializeStore(); const json = JSON.parsePackageJSONUTF8AlwaysDecode(&package_json_source, manager.log, manager.allocator) catch |err| { - manager.pauseProgress(); switch (Output.enable_ansi_colors) { inline else => |enable_ansi_colors| { manager.log.printForLogLevelWithEnableAnsiColors(Output.errorWriter(), enable_ansi_colors) catch {}; @@ -11532,8 +11468,8 @@ pub const PackageManager = struct { if (json.asProperty("version")) |v| { if (v.expr.asString(manager.allocator)) |s| break :version s; } - manager.cleanErrorGeneric( - "invalid package.json, missing or invalid property \"version\": {s}", + Output.prettyError( + "error: invalid package.json, missing or invalid property \"version\": {s}\n", .{package_json_source.path.text}, ); Global.crash(); @@ -11544,8 +11480,8 @@ pub const PackageManager = struct { const name = lockfile.str(&package.name); const actual_package = switch (lockfile.package_index.get(package.name_hash) orelse { - manager.cleanErrorGeneric( - "failed to find package in lockfile package index, this is a bug in Bun. Please file a GitHub issue.", + Output.prettyError( + "error: failed to find package in lockfile package index, this is a bug in Bun. Please file a GitHub issue.\n", .{}, ); Global.crash(); @@ -11559,7 +11495,7 @@ pub const PackageManager = struct { break :brk pkg; } } - manager.cleanErrorGeneric("could not find package with name: {s}", .{ + Output.prettyError("error: could not find package with name: {s}\n", .{ package.name.slice(lockfile.buffers.string_bytes.items), }); Global.crash(); @@ -11581,7 +11517,7 @@ pub const PackageManager = struct { }, .name_and_version => brk: { const name, const version = Dependency.splitNameAndVersion(argument); - const pkg_id, const node_modules = pkgInfoForNameAndVersion(manager, lockfile, &iterator, argument, name, version); + const pkg_id, const node_modules = pkgInfoForNameAndVersion(lockfile, &iterator, argument, name, version); const changes_dir = bun.path.joinZBuf(pathbuf[0..], &[_][]const u8{ node_modules.relative_path, @@ -11618,8 +11554,8 @@ pub const PackageManager = struct { const cache_dir_path = switch (bun.sys.getFdPath(bun.toFD(cache_dir.fd), &buf2)) { .result => |s| s, .err => |e| { - manager.cleanErrorGeneric( - "failed to read from cache {}", + Output.prettyError( + "error: failed to read from cache {}\n", .{e.toSystemError()}, ); Global.crash(); @@ -11632,8 +11568,8 @@ pub const PackageManager = struct { }; const random_tempdir = bun.span(bun.fs.FileSystem.instance.tmpname("node_modules_tmp", buf2[0..], bun.fastRandom()) catch |e| { - manager.cleanErrorGeneric( - "failed to make tempdir {s}", + Output.prettyError( + "error: failed to make tempdir {s}\n", .{@errorName(e)}, ); Global.crash(); @@ -11646,8 +11582,8 @@ pub const PackageManager = struct { // will `rename()` it out and back again. const has_nested_node_modules = has_nested_node_modules: { var new_folder_handle = std.fs.cwd().openDir(new_folder, .{}) catch |e| { - manager.cleanErrorGeneric( - "failed to open directory {s} {s}", + Output.prettyError( + "error: failed to open directory {s} {s}\n", .{ new_folder, @errorName(e) }, ); Global.crash(); @@ -11666,8 +11602,8 @@ pub const PackageManager = struct { }; const patch_tag_tmpname = bun.span(bun.fs.FileSystem.instance.tmpname("patch_tmp", buf3[0..], bun.fastRandom()) catch |e| { - manager.cleanErrorGeneric( - "failed to make tempdir {s}", + Output.prettyError( + "error: failed to make tempdir {s}\n", .{@errorName(e)}, ); Global.crash(); @@ -11687,8 +11623,8 @@ pub const PackageManager = struct { break :has_bun_patch_tag null; }; var new_folder_handle = std.fs.cwd().openDir(new_folder, .{}) catch |e| { - manager.cleanErrorGeneric( - "failed to open directory {s} {s}", + Output.prettyError( + "error: failed to open directory {s} {s}\n", .{ new_folder, @errorName(e) }, ); Global.crash(); @@ -11702,7 +11638,7 @@ pub const PackageManager = struct { patch_tag_tmpname, .{ .move_fallback = true }, ).asErr()) |e| { - manager.cleanWarnResume("failed renaming the bun patch tag, this may cause issues: {}", .{e}); + Output.warn("failed renaming the bun patch tag, this may cause issues: {}", .{e}); break :has_bun_patch_tag null; } break :has_bun_patch_tag patch_tag; @@ -11710,8 +11646,8 @@ pub const PackageManager = struct { defer { if (has_nested_node_modules or bun_patch_tag != null) { var new_folder_handle = std.fs.cwd().openDir(new_folder, .{}) catch |e| { - manager.cleanErrorGeneric( - "failed to open directory {s} {s}", + Output.prettyError( + "error: failed to open directory {s} {s}\n", .{ new_folder, @errorName(e) }, ); Global.crash(); @@ -11726,7 +11662,7 @@ pub const PackageManager = struct { "node_modules", .{ .move_fallback = true }, ).asErr()) |e| { - manager.cleanWarnResume("failed renaming nested node_modules folder, this may cause issues: {}", .{e}); + Output.warn("failed renaming nested node_modules folder, this may cause issues: {}", .{e}); } } @@ -11738,7 +11674,7 @@ pub const PackageManager = struct { patch_tag, .{ .move_fallback = true }, ).asErr()) |e| { - manager.cleanWarnResume("failed renaming the bun patch tag, this may cause issues: {}", .{e}); + Output.warn("failed renaming the bun patch tag, this may cause issues: {}", .{e}); } } } @@ -11748,8 +11684,8 @@ pub const PackageManager = struct { const cwd = switch (bun.sys.getcwdZ(&cwdbuf)) { .result => |fd| fd, .err => |e| { - manager.cleanErrorGeneric( - "failed to get cwd path {}", + Output.prettyError( + "error: failed to get cwd path {}\n", .{e}, ); Global.crash(); @@ -11757,8 +11693,8 @@ pub const PackageManager = struct { }; var gitbuf: bun.PathBuffer = undefined; const git = bun.which(&gitbuf, bun.getenvZ("PATH") orelse "", cwd, "git") orelse { - manager.cleanErrorGeneric( - "git must be installed to use `bun patch --commit`", + Output.prettyError( + "error: git must be installed to use `bun patch --commit` \n", .{}, ); Global.crash(); @@ -11767,16 +11703,16 @@ pub const PackageManager = struct { const opts = bun.patch.spawnOpts(paths[0], paths[1], cwd, git, &manager.event_loop); var spawn_result = switch (bun.spawnSync(&opts) catch |e| { - manager.cleanErrorGeneric( - "failed to make diff {s}", + Output.prettyError( + "error: failed to make diff {s}\n", .{@errorName(e)}, ); Global.crash(); }) { .result => |r| r, .err => |e| { - manager.cleanErrorGeneric( - "failed to make diff {}", + Output.prettyError( + "error: failed to make diff {}\n", .{e}, ); Global.crash(); @@ -11784,8 +11720,8 @@ pub const PackageManager = struct { }; const contents = switch (bun.patch.diffPostProcess(&spawn_result, paths[0], paths[1]) catch |e| { - manager.cleanErrorGeneric( - "failed to make diff {s}", + Output.prettyError( + "error: failed to make diff {s}\n", .{@errorName(e)}, ); Global.crash(); @@ -11808,8 +11744,8 @@ pub const PackageManager = struct { } else try writer.print("{s}", .{this.stderr.items[0..]}); } }; - manager.cleanErrorGeneric( - "failed to make diff {}", + Output.prettyError( + "error: failed to make diff {}\n", .{ Truncate{ .stderr = stderr }, }, @@ -12769,8 +12705,6 @@ pub const PackageManager = struct { .symlink => { const directory = this.manager.globalLinkDir() catch |err| { if (comptime log_level != .silent) { - this.manager.pauseProgress(); - defer this.manager.resumeProgress(); const fmt = "\nerror: unable to access global directory while installing {s}: {s}\n"; const args = .{ name, @errorName(err) }; @@ -12949,7 +12883,7 @@ pub const PackageManager = struct { // creating this directory now, right before installing package var destination_dir = this.node_modules.makeAndOpenDir(this.root_node_modules_folder) catch |err| { if (log_level != .silent) { - this.manager.cleanErrorResume(err, "Failed to open node_modules folder for {s} in {s}", .{ + Output.err(err, "Failed to open node_modules folder for {s} in {s}", .{ name, bun.fmt.fmtPath(u8, this.node_modules.path.items, .{}), }); @@ -13033,8 +12967,6 @@ pub const PackageManager = struct { const count = this.getInstalledPackageScriptsCount(alias, package_id, resolution.tag, destination_dir, log_level); if (count > 0) { if (comptime log_level.isVerbose()) { - this.manager.pauseProgress(); - defer this.manager.resumeProgress(); Output.prettyError("Blocked {d} scripts for: {s}@{}\n", .{ count, alias, @@ -13060,8 +12992,8 @@ pub const PackageManager = struct { if (!pkg_has_patch) this.incrementTreeInstallCount(this.current_tree_id, destination_dir, !is_pending_package_install, log_level); if (cause.err == error.DanglingSymlink) { - this.manager.cleanErrorGenericResume( - "{s} \"link:{s}\" not found (try running 'bun link' in the intended package's folder)", + Output.prettyErrorln( + "error: {s} \"link:{s}\" not found (try running 'bun link' in the intended package's folder)", .{ @errorName(cause.err), this.names[package_id].slice(buf) }, ); this.summary.fail += 1; @@ -13077,7 +13009,7 @@ pub const PackageManager = struct { if (!Singleton.node_modules_is_ok) { if (!Environment.isWindows) { const stat = bun.sys.fstat(bun.toFD(destination_dir)).unwrap() catch |err| { - this.manager.cleanError("EACCES", "Permission denied while installing {s}", .{ + Output.err("EACCES", "Permission denied while installing {s}", .{ this.names[package_id].slice(buf), }); if (Environment.isDebug) { @@ -13094,21 +13026,21 @@ pub const PackageManager = struct { stat.mode & bun.S.IWOTH > 0; if (!is_writable) { - this.manager.cleanError("EACCES", "Permission denied while writing packages into node_modules.", .{}); + Output.err("EACCES", "Permission denied while writing packages into node_modules.", .{}); Global.exit(1); } } Singleton.node_modules_is_ok = true; } - this.manager.cleanErrorResume("EACCES", "Permission denied while installing {s}", .{ + Output.err("EACCES", "Permission denied while installing {s}", .{ this.names[package_id].slice(buf), }); this.summary.fail += 1; } else { - this.manager.cleanErrorGenericResume( - "{s} installing {s} ({s})", + Output.prettyErrorln( + "error: {s} installing {s} ({s})", .{ @errorName(cause.err), this.names[package_id].slice(buf), install_result.fail.step.name() }, ); this.summary.fail += 1; @@ -13122,7 +13054,7 @@ pub const PackageManager = struct { var destination_dir = this.node_modules.makeAndOpenDir(this.root_node_modules_folder) catch |err| { if (log_level != .silent) { - this.manager.cleanError(err, "Failed to open node_modules folder for {s} in {s}", .{ + Output.err(err, "Failed to open node_modules folder for {s} in {s}", .{ name, bun.fmt.fmtPath(u8, this.node_modules.path.items, .{}), }); @@ -13193,8 +13125,6 @@ pub const PackageManager = struct { resolution, ) catch |err| { if (comptime log_level != .silent) { - this.manager.pauseProgress(); - defer this.manager.resumeProgress(); const fmt = "\nerror: failed to enqueue lifecycle scripts for {s}: {s}\n"; const args = .{ folder_name, @errorName(err) }; @@ -13265,8 +13195,6 @@ pub const PackageManager = struct { this.node.completeOne(); } if (comptime log_level.isVerbose()) { - this.manager.pauseProgress(); - defer this.manager.resumeProgress(); const name = this.lockfile.str(&this.names[package_id]); if (!meta.os.isMatch() and !meta.arch.isMatch()) { Output.prettyErrorln("Skip installing '{s}' cpu & os mismatch", .{name}); @@ -13534,7 +13462,7 @@ pub const PackageManager = struct { } }; break :brk bun.openDir(cwd, "node_modules") catch |err| { - this.cleanErrorGeneric("{s} opening node_modules folder", .{@errorName(err)}); + Output.prettyErrorln("error: {s} opening node_modules folder", .{@errorName(err)}); Global.crash(); }; }; @@ -13787,8 +13715,6 @@ pub const PackageManager = struct { } if (PackageManager.verbose_install and PackageManager.instance.pendingTaskCount() > 0) { - closure.manager.pauseProgress(); - defer closure.manager.resumeProgress(); if (PackageManager.hasEnoughTimePassedBetweenWaitingMessages()) Output.prettyErrorln("[PackageManager] waiting for {d} tasks\n", .{PackageManager.instance.pendingTaskCount()}); } @@ -13837,8 +13763,6 @@ pub const PackageManager = struct { while (this.pending_lifecycle_script_tasks.load(.monotonic) > 0) { if (PackageManager.verbose_install) { - this.pauseProgress(); - defer this.resumeProgress(); if (PackageManager.hasEnoughTimePassedBetweenWaitingMessages()) Output.prettyErrorln("[PackageManager] waiting for {d} scripts\n", .{this.pending_lifecycle_script_tasks.load(.monotonic)}); } @@ -13974,6 +13898,7 @@ pub const PackageManager = struct { // this defaults to false // but we force allowing updates to the lockfile when you do bun add var had_any_diffs = false; + manager.progress = .{}; // Step 2. Parse the package.json file const root_package_json_source = logger.Source.initPathString(package_json_cwd, root_package_json_contents); @@ -13982,16 +13907,16 @@ pub const PackageManager = struct { .err => |cause| { if (log_level != .silent) { switch (cause.step) { - .open_file => Output.errGeneric("failed to open lockfile: {s}", .{ + .open_file => Output.prettyError("error opening lockfile: {s}\n", .{ @errorName(cause.value), }), - .parse_file => Output.errGeneric("failed to parse lockfile: {s}", .{ + .parse_file => Output.prettyError("error parsing lockfile: {s}\n", .{ @errorName(cause.value), }), - .read_file => Output.errGeneric("failed to read lockfile: {s}", .{ + .read_file => Output.prettyError("error reading lockfile: {s}\n", .{ @errorName(cause.value), }), - .migrating => Output.errGeneric("failed to migrate lockfile: {s}", .{ + .migrating => Output.prettyError("error migrating lockfile: {s}\n", .{ @errorName(cause.value), }), } @@ -14295,7 +14220,7 @@ pub const PackageManager = struct { if (manager.options.enable.frozen_lockfile and load_lockfile_result != .not_found) { if (comptime log_level != .silent) { - manager.cleanErrorGeneric("lockfile had changes, but lockfile is frozen", .{}); + Output.prettyErrorln("error: lockfile had changes, but lockfile is frozen", .{}); } Global.crash(); } @@ -14390,8 +14315,6 @@ pub const PackageManager = struct { const pending_tasks = this.pendingTaskCount(); if (PackageManager.verbose_install and pending_tasks > 0) { - this.pauseProgress(); - defer this.resumeProgress(); if (PackageManager.hasEnoughTimePassedBetweenWaitingMessages()) Output.prettyErrorln("[PackageManager] waiting for {d} tasks\n", .{pending_tasks}); } @@ -14438,10 +14361,8 @@ pub const PackageManager = struct { } const had_errors_before_cleaning_lockfile = manager.log.hasErrors(); - manager.pauseProgress(); try manager.log.printForLogLevel(Output.errorWriter()); manager.log.reset(); - manager.resumeProgress(); // This operation doesn't perform any I/O, so it should be relatively cheap. manager.lockfile = try manager.lockfile.cleanWithLogger( @@ -14539,8 +14460,6 @@ pub const PackageManager = struct { if (manager.options.enable.frozen_lockfile and load_lockfile_result != .not_found) { if (manager.lockfile.hasMetaHashChanged(PackageManager.verbose_install or manager.options.do.print_meta_hash_string, packages_len_before_install) catch false) { if (comptime log_level != .silent) { - manager.pauseProgress(); - defer manager.resumeProgress(); Output.prettyErrorln("error: lockfile had changes, but lockfile is frozen", .{}); Output.note("try re-running without --frozen-lockfile and commit the updated lockfile", .{}); } @@ -14559,8 +14478,6 @@ pub const PackageManager = struct { } if (comptime log_level != .silent) { - manager.pauseProgress(); - defer manager.resumeProgress(); try manager.log.printForLogLevel(Output.errorWriter()); } if (had_errors_before_cleaning_lockfile or manager.log.hasErrors()) Global.crash(); @@ -14594,18 +14511,12 @@ pub const PackageManager = struct { break :brk; } - if (log_level != .silent) { - manager.pauseProgress(); - defer manager.resumeProgress(); - Output.prettyErrorln("\nerror: {s} deleting empty lockfile", .{@errorName(err)}); - } + if (log_level != .silent) Output.prettyErrorln("\nerror: {s} deleting empty lockfile", .{@errorName(err)}); break :save; }; } if (!manager.options.global) { if (log_level != .silent) { - manager.pauseProgress(); - defer manager.resumeProgress(); switch (manager.subcommand) { .remove => Output.prettyErrorln("\npackage.json has no dependencies! Deleted empty lockfile", .{}), else => Output.prettyErrorln("No packages! Deleted empty lockfile", .{}), @@ -14686,11 +14597,7 @@ pub const PackageManager = struct { while (manager.pending_lifecycle_script_tasks.load(.monotonic) > 0) { if (PackageManager.verbose_install) { - if (PackageManager.hasEnoughTimePassedBetweenWaitingMessages()) { - manager.pauseProgress(); - defer manager.resumeProgress(); - Output.prettyErrorln("[PackageManager] waiting for {d} scripts\n", .{manager.pending_lifecycle_script_tasks.load(.monotonic)}); - } + if (PackageManager.hasEnoughTimePassedBetweenWaitingMessages()) Output.prettyErrorln("[PackageManager] waiting for {d} scripts\n", .{manager.pending_lifecycle_script_tasks.load(.monotonic)}); } manager.sleep(); @@ -14847,8 +14754,6 @@ pub const PackageManager = struct { this.options.remote_package_features, )) continue; if (log_level != .silent) { - this.pauseProgress(); - defer this.resumeProgress(); if (failed_dep.name.isEmpty() or strings.eqlLong(failed_dep.name.slice(string_buf), failed_dep.version.literal.slice(string_buf), true)) { Output.errGeneric("{} failed to resolve", .{ failed_dep.version.literal.fmt(string_buf), diff --git a/src/install/lifecycle_script_runner.zig b/src/install/lifecycle_script_runner.zig index 652a0ef353c94..f9df8bfd639f2 100644 --- a/src/install/lifecycle_script_runner.zig +++ b/src/install/lifecycle_script_runner.zig @@ -437,8 +437,6 @@ pub const LifecycleScriptSubprocess = struct { }); if (comptime log_level.isVerbose()) { - manager.pauseProgress(); - defer manager.resumeProgress(); Output.prettyErrorln("[Scripts] Starting scripts for \"{s}\"", .{ list.package_name, }); @@ -447,8 +445,6 @@ pub const LifecycleScriptSubprocess = struct { _ = manager.pending_lifecycle_script_tasks.fetchAdd(1, .monotonic); lifecycle_subprocess.spawnNextScript(list.first_index) catch |err| { - manager.pauseProgress(); - defer manager.resumeProgress(); Output.prettyErrorln("error: Failed to run script {s} due to error {s}", .{ Lockfile.Scripts.names[list.first_index], @errorName(err), From dbe749790ea3c9b3fa13d37174020cc11fdba38f Mon Sep 17 00:00:00 2001 From: Dylan Conway Date: Thu, 10 Oct 2024 16:01:13 -0700 Subject: [PATCH 17/19] revert http revert --- src/http.zig | 61 ++++++++++++++++++++++++++++------------------------ 1 file changed, 33 insertions(+), 28 deletions(-) diff --git a/src/http.zig b/src/http.zig index 424adab91d3ea..de3a58fbec4d4 100644 --- a/src/http.zig +++ b/src/http.zig @@ -516,6 +516,13 @@ pub const HTTPCertError = struct { reason: [:0]const u8 = "", }; +pub const InitError = error{ + FailedToOpenSocket, + LoadCAFile, + InvalidCAFile, + InvalidCA, +}; + fn NewHTTPContext(comptime ssl: bool) type { return struct { const pool_size = 64; @@ -585,13 +592,6 @@ fn NewHTTPContext(comptime ssl: bool) type { bun.default_allocator.destroy(this); } - pub const InitError = error{ - FailedToOpenSocket, - LoadCAFile, - InvalidCAFile, - InvalidCA, - }; - pub fn initWithClientConfig(this: *@This(), client: *HTTPClient) InitError!void { if (!comptime ssl) { @compileError("ssl only"); @@ -1041,9 +1041,34 @@ pub const HTTPThread = struct { return this.lazy_libdeflater.?; } + fn onInitErrorNoop(err: InitError, opts: InitOpts) noreturn { + switch (err) { + error.LoadCAFile => { + if (!bun.sys.existsZ(opts.abs_ca_file_name)) { + Output.err("HTTPThread", "failed to find CA file: '{s}'", .{opts.abs_ca_file_name}); + } else { + Output.err("HTTPThread", "failed to load CA file: '{s}'", .{opts.abs_ca_file_name}); + } + }, + error.InvalidCAFile => { + Output.err("HTTPThread", "the CA file is invalid: '{s}'", .{opts.abs_ca_file_name}); + }, + error.InvalidCA => { + Output.err("HTTPThread", "the provided CA is invalid", .{}); + }, + error.FailedToOpenSocket => { + Output.errGeneric("failed to start HTTP client thread", .{}); + }, + } + Global.crash(); + } + pub const InitOpts = struct { ca: []stringZ = &.{}, abs_ca_file_name: stringZ = &.{}, + for_install: bool = false, + + onInitError: *const fn (err: InitError, opts: InitOpts) noreturn = &onInitErrorNoop, }; fn initOnce(opts: *const InitOpts) void { @@ -1088,27 +1113,7 @@ pub const HTTPThread = struct { http_thread.loop = loop; http_thread.http_context.init(); - http_thread.https_context.initWithThreadOpts(&opts) catch |err| { - switch (err) { - error.LoadCAFile => { - if (!bun.sys.existsZ(opts.abs_ca_file_name)) { - Output.err("HTTPThread", "failed to find CA file: '{s}'", .{opts.abs_ca_file_name}); - } else { - Output.err("HTTPThread", "failed to load CA file: '{s}'", .{opts.abs_ca_file_name}); - } - }, - error.InvalidCAFile => { - Output.err("HTTPThread", "the CA file is invalid: '{s}'", .{opts.abs_ca_file_name}); - }, - error.InvalidCA => { - Output.err("HTTPThread", "the provided CA is invalid", .{}); - }, - error.FailedToOpenSocket => { - Output.errGeneric("failed to start HTTP client thread", .{}); - }, - } - Global.crash(); - }; + http_thread.https_context.initWithThreadOpts(&opts) catch |err| opts.onInitError(err, opts); http_thread.has_awoken.store(true, .monotonic); http_thread.processEvents(); } From 358e5c9a95166dee4cc020cba334cbe30ac17d7f Mon Sep 17 00:00:00 2001 From: Dylan Conway Date: Thu, 10 Oct 2024 16:06:15 -0700 Subject: [PATCH 18/19] fix tests --- src/install/install.zig | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/install/install.zig b/src/install/install.zig index 1204add2b267e..5ea3f2d8b5c70 100644 --- a/src/install/install.zig +++ b/src/install/install.zig @@ -8358,19 +8358,19 @@ pub const PackageManager = struct { switch (err) { error.LoadCAFile => { if (!bun.sys.existsZ(opts.abs_ca_file_name)) { - PackageManager.instance.cleanError("HTTPThread", "could not find CA file: '{s}'", .{opts.abs_ca_file_name}); + Output.err("HTTPThread", "could not find CA file: '{s}'", .{opts.abs_ca_file_name}); } else { - PackageManager.instance.cleanError("HTTPThread", "invalid CA file: '{s}'", .{opts.abs_ca_file_name}); + Output.err("HTTPThread", "invalid CA file: '{s}'", .{opts.abs_ca_file_name}); } }, error.InvalidCAFile => { - PackageManager.instance.cleanError("HTTPThread", "invalid CA file: '{s}'", .{opts.abs_ca_file_name}); + Output.err("HTTPThread", "invalid CA file: '{s}'", .{opts.abs_ca_file_name}); }, error.InvalidCA => { - PackageManager.instance.cleanError("HTTPThread", "the CA is invalid", .{}); + Output.err("HTTPThread", "the CA is invalid", .{}); }, error.FailedToOpenSocket => { - PackageManager.instance.cleanErrorGeneric("failed to start HTTP client thread", .{}); + Output.errGeneric("failed to start HTTP client thread", .{}); }, } Global.crash(); @@ -8748,6 +8748,7 @@ pub const PackageManager = struct { HTTP.HTTPThread.init(&.{ .ca = ca, .abs_ca_file_name = abs_ca_file_name, + .onInitError = &httpThreadOnInitError, }); manager.timestamp_for_manifest_cache_control = brk: { From 777d0714c89a6581694f924f5ee4c8bf5e61d6b6 Mon Sep 17 00:00:00 2001 From: Dylan Conway Date: Fri, 11 Oct 2024 01:11:10 -0700 Subject: [PATCH 19/19] read ca and cafile from npmrc --- src/ini.zig | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/ini.zig b/src/ini.zig index 0a2e9cb564999..cc9deecd0bbf4 100644 --- a/src/ini.zig +++ b/src/ini.zig @@ -962,6 +962,32 @@ pub fn loadNpmrc( } } + if (out.asProperty("ca")) |query| { + if (query.expr.asUtf8StringLiteral()) |str| { + install.ca = .{ + .str = str, + }; + } else if (query.expr.isArray()) { + const arr = query.expr.data.e_array; + var list = try allocator.alloc([]const u8, arr.items.len); + var i: usize = 0; + for (arr.items.slice()) |item| { + list[i] = try item.asStringCloned(allocator) orelse continue; + i += 1; + } + + install.ca = .{ + .list = list, + }; + } + } + + if (out.asProperty("cafile")) |query| { + if (try query.expr.asStringCloned(allocator)) |cafile| { + install.cafile = cafile; + } + } + var registry_map = install.scoped orelse bun.Schema.Api.NpmRegistryMap{}; // Process scopes