Merge remote-tracking branch 'origin/main' into pfg/bun-test-diff

This commit is contained in:
pfg
2025-07-29 14:22:59 -07:00
32 changed files with 14563 additions and 155 deletions

View File

@@ -168,5 +168,5 @@
"WebKit/WebInspectorUI": true,
},
"git.detectSubmodules": false,
// "bun.test.customScript": "./build/debug/bun-debug test"
"bun.test.customScript": "./build/debug/bun-debug test"
}

View File

@@ -547,6 +547,7 @@ src/http/AsyncHTTP.zig
src/http/CertificateInfo.zig
src/http/Decompressor.zig
src/http/Encoding.zig
src/http/ETag.zig
src/http/FetchRedirect.zig
src/http/HeaderBuilder.zig
src/http/Headers.zig
@@ -634,6 +635,7 @@ src/install/resolvers/folder_resolver.zig
src/install/versioned_url.zig
src/install/windows-shim/BinLinkingShim.zig
src/install/windows-shim/bun_shim_impl.zig
src/install/yarn.zig
src/interchange.zig
src/interchange/json.zig
src/interchange/toml.zig

View File

@@ -646,11 +646,10 @@ pub fn deinit(dev: *DevServer) void {
.next_bundle = {
var r = dev.next_bundle.requests.first;
while (r) |request| {
defer dev.deferred_request_pool.put(request);
// TODO: deinitializing in this state is almost certainly an assertion failure.
// This code is shipped in release because it is only reachable by experimenntal server components.
bun.debugAssert(request.data.handler != .server_handler);
request.data.deinit();
defer request.data.deref();
r = request.next;
}
dev.next_bundle.route_queue.deinit(allocator);
@@ -1088,6 +1087,8 @@ fn deferRequest(
const method = bun.http.Method.which(req.method()) orelse .POST;
deferred.data = .{
.route_bundle_index = route_bundle_index,
.dev = dev,
.ref_count = .init(),
.handler = switch (kind) {
.bundled_html_page => .{ .bundled_html_page = .{ .response = resp, .method = method } },
.server_handler => .{
@@ -1095,6 +1096,7 @@ fn deferRequest(
},
},
};
deferred.data.ref();
resp.onAborted(*DeferredRequest, DeferredRequest.onAbort, &deferred.data);
requests_array.prepend(deferred);
}
@@ -1533,8 +1535,20 @@ pub const DeferredRequest = struct {
pub const List = std.SinglyLinkedList(DeferredRequest);
pub const Node = List.Node;
const RefCount = bun.ptr.RefCount(@This(), "ref_count", deinitImpl, .{});
route_bundle_index: RouteBundle.Index,
handler: Handler,
dev: *DevServer,
/// This struct can have at most 2 references it:
/// - The dev server (`dev.current_bundle.requests`)
/// - uws.Response as a user data pointer
ref_count: RefCount,
// expose `ref` and `deref` as public methods
pub const ref = RefCount.ref;
pub const deref = RefCount.deref;
const Handler = union(enum) {
/// For a .framework route. This says to call and render the page.
@@ -1560,10 +1574,15 @@ pub const DeferredRequest = struct {
assert(this.handler == .aborted);
}
/// *WARNING*: Do not call this directly, instead call `.deref()`
///
/// Calling this is only required if the desired handler is going to be avoided,
/// such as for bundling failures or aborting the server.
/// Does not free the underlying `DeferredRequest.Node`
fn deinit(this: *DeferredRequest) void {
fn deinitImpl(this: *DeferredRequest) void {
bun.assert(this.ref_count.active_counts == 0);
defer this.dev.deferred_request_pool.put(@fieldParentPtr("data", this));
switch (this.handler) {
.server_handler => |*saved| saved.deinit(),
.bundled_html_page, .aborted => {},
@@ -1572,17 +1591,18 @@ pub const DeferredRequest = struct {
/// Deinitializes state by aborting the connection.
fn abort(this: *DeferredRequest) void {
switch (this.handler) {
var handler = this.handler;
this.handler = .aborted;
switch (handler) {
.server_handler => |*saved| {
saved.response.endWithoutBody(true);
saved.deinit();
saved.ctx.onAbort(saved.response);
saved.js_request.deinit();
},
.bundled_html_page => |r| {
r.response.endWithoutBody(true);
},
.aborted => return,
.aborted => {},
}
this.handler = .aborted;
}
};
@@ -1997,8 +2017,8 @@ pub fn finalizeBundle(
Output.debug("current_bundle.requests.first != null. this leaves pending requests without an error page!", .{});
}
while (current_bundle.requests.popFirst()) |node| {
defer dev.deferred_request_pool.put(node);
const req = &node.data;
defer req.deref();
req.abort();
}
}
@@ -2504,8 +2524,8 @@ pub fn finalizeBundle(
var inspector_agent = dev.inspector();
while (current_bundle.requests.popFirst()) |node| {
defer dev.deferred_request_pool.put(node);
const req = &node.data;
defer req.deref();
const rb = dev.routeBundlePtr(req.route_bundle_index);
rb.server_state = .possible_bundling_failures;
@@ -2608,8 +2628,8 @@ pub fn finalizeBundle(
defer dev.graph_safety_lock.lock();
while (current_bundle.requests.popFirst()) |node| {
defer dev.deferred_request_pool.put(node);
const req = &node.data;
defer req.deref();
const rb = dev.routeBundlePtr(req.route_bundle_index);
rb.server_state = .loaded;
@@ -3951,7 +3971,7 @@ pub fn onPluginsResolved(dev: *DevServer, plugins: ?*Plugin) !void {
pub fn onPluginsRejected(dev: *DevServer) !void {
dev.plugin_state = .err;
while (dev.next_bundle.requests.popFirst()) |item| {
defer dev.deferred_request_pool.put(item);
defer item.data.deref();
item.data.abort();
}
dev.next_bundle.route_queue.clearRetainingCapacity();

View File

@@ -199,6 +199,28 @@ pub fn getRequest(self: AnyRequestContext) ?*uws.Request {
}
}
pub fn onAbort(self: AnyRequestContext, response: uws.AnyResponse) void {
if (self.tagged_pointer.isNull()) {
return;
}
switch (self.tagged_pointer.tag()) {
@field(Pointer.Tag, bun.meta.typeBaseName(@typeName(HTTPServer.RequestContext))) => {
self.tagged_pointer.as(HTTPServer.RequestContext).onAbort(response.TCP);
},
@field(Pointer.Tag, bun.meta.typeBaseName(@typeName(HTTPSServer.RequestContext))) => {
self.tagged_pointer.as(HTTPSServer.RequestContext).onAbort(response.SSL);
},
@field(Pointer.Tag, bun.meta.typeBaseName(@typeName(DebugHTTPServer.RequestContext))) => {
self.tagged_pointer.as(DebugHTTPServer.RequestContext).onAbort(response.TCP);
},
@field(Pointer.Tag, bun.meta.typeBaseName(@typeName(DebugHTTPSServer.RequestContext))) => {
self.tagged_pointer.as(DebugHTTPSServer.RequestContext).onAbort(response.SSL);
},
else => @panic("Unexpected AnyRequestContext tag"),
}
}
pub fn deref(self: AnyRequestContext) void {
if (self.tagged_pointer.isNull()) {
return;

View File

@@ -37,12 +37,9 @@ pub fn initFromAnyBlob(blob: *const AnyBlob, options: InitFromBytesOptions) *Sta
// Generate ETag if not already present
if (headers.get("etag") == null) {
const slice = blob.slice();
const hash = std.hash.XxHash64.hash(0, slice);
var etag_buf: [32]u8 = undefined;
const etag_str = std.fmt.bufPrint(&etag_buf, "\"{x}\"", .{hash}) catch bun.outOfMemory();
headers.append("ETag", etag_str) catch bun.outOfMemory();
if (blob.slice().len > 0) {
ETag.appendToHeaders(blob.slice(), &headers) catch bun.outOfMemory();
}
}
return bun.new(StaticRoute, .{
@@ -151,16 +148,9 @@ pub fn fromJS(globalThis: *jsc.JSGlobalObject, argument: jsc.JSValue) bun.JSErro
// Generate ETag if not already present
if (headers.get("etag") == null) {
const slice = blob.slice();
const hash = std.hash.XxHash64.hash(0, slice);
var etag_buf: [32]u8 = undefined;
const etag_str = std.fmt.bufPrint(&etag_buf, "\"{x}\"", .{hash}) catch bun.outOfMemory();
headers.append("ETag", etag_str) catch {
var mutable_blob = blob;
mutable_blob.detach();
return globalThis.throwOutOfMemory();
};
if (blob.slice().len > 0) {
try ETag.appendToHeaders(blob.slice(), &headers);
}
}
return bun.new(StaticRoute, .{
@@ -181,18 +171,7 @@ pub fn fromJS(globalThis: *jsc.JSGlobalObject, argument: jsc.JSValue) bun.JSErro
pub fn onHEADRequest(this: *StaticRoute, req: *uws.Request, resp: AnyResponse) void {
// Check If-None-Match for HEAD requests with 200 status
if (this.status_code == 200) {
if (evaluateIfNoneMatch(this, req)) {
// Return 304 Not Modified
req.setYield(false);
this.ref();
if (this.server) |server| {
server.onPendingRequest();
resp.timeout(server.config().idleTimeout);
}
this.doWriteStatus(304, resp);
this.doWriteHeaders(resp);
resp.endWithoutBody(resp.shouldCloseConnection());
this.onResponseComplete(resp);
if (this.render304NotModifiedIfNoneMatch(req, resp)) {
return;
}
}
@@ -235,18 +214,7 @@ pub fn onRequest(this: *StaticRoute, req: *uws.Request, resp: AnyResponse) void
pub fn onGET(this: *StaticRoute, req: *uws.Request, resp: AnyResponse) void {
// Check If-None-Match for GET requests with 200 status
if (this.status_code == 200) {
if (evaluateIfNoneMatch(this, req)) {
// Return 304 Not Modified
req.setYield(false);
this.ref();
if (this.server) |server| {
server.onPendingRequest();
resp.timeout(server.config().idleTimeout);
}
this.doWriteStatus(304, resp);
this.doWriteHeaders(resp);
resp.endWithoutBody(resp.shouldCloseConnection());
this.onResponseComplete(resp);
if (this.render304NotModifiedIfNoneMatch(req, resp)) {
return;
}
}
@@ -292,7 +260,7 @@ fn onResponseComplete(this: *StaticRoute, resp: AnyResponse) void {
this.deref();
}
pub fn doRenderBlob(this: *StaticRoute, resp: AnyResponse, did_finish: *bool) void {
fn doRenderBlob(this: *StaticRoute, resp: AnyResponse, did_finish: *bool) void {
// We are not corked
// The body is small
// Faster to do the memcpy than to do the two network calls
@@ -305,7 +273,7 @@ pub fn doRenderBlob(this: *StaticRoute, resp: AnyResponse, did_finish: *bool) vo
}
}
pub fn doRenderBlobCorked(this: *StaticRoute, resp: AnyResponse, did_finish: *bool) void {
fn doRenderBlobCorked(this: *StaticRoute, resp: AnyResponse, did_finish: *bool) void {
this.renderMetadata(resp);
this.renderBytes(resp, did_finish);
}
@@ -382,68 +350,42 @@ pub fn onWithMethod(this: *StaticRoute, method: bun.http.Method, resp: AnyRespon
}
}
/// Parse a single entity tag from a string, returns the tag without quotes and whether it's weak
fn parseEntityTag(tag_str: []const u8) struct { tag: []const u8, is_weak: bool } {
var str = std.mem.trim(u8, tag_str, " \t");
// Check for weak indicator
var is_weak = false;
if (std.mem.startsWith(u8, str, "W/")) {
is_weak = true;
str = str[2..];
str = std.mem.trimLeft(u8, str, " \t");
}
// Remove surrounding quotes
if (str.len >= 2 and str[0] == '"' and str[str.len - 1] == '"') {
str = str[1 .. str.len - 1];
}
return .{ .tag = str, .is_weak = is_weak };
}
/// Perform weak comparison between two entity tags according to RFC 9110 Section 8.8.3.2
fn weakEntityTagMatch(tag1: []const u8, is_weak1: bool, tag2: []const u8, is_weak2: bool) bool {
_ = is_weak1;
_ = is_weak2;
// For weak comparison, we only compare the opaque tag values, ignoring weak indicators
return std.mem.eql(u8, tag1, tag2);
}
/// Evaluate If-None-Match condition according to RFC 9110 Section 13.1.2
fn evaluateIfNoneMatch(this: *StaticRoute, req: *uws.Request) bool {
fn render304NotModifiedIfNoneMatch(this: *StaticRoute, req: *uws.Request, resp: AnyResponse) bool {
const if_none_match = req.header("if-none-match") orelse return false;
// Get the ETag from our headers
const our_etag = this.headers.get("etag") orelse return false;
const our_parsed = parseEntityTag(our_etag);
// Handle "*" case
if (std.mem.eql(u8, std.mem.trim(u8, if_none_match, " \t"), "*")) {
return true; // Condition is false, so we should return 304
const etag = this.headers.get("etag") orelse return false;
if (if_none_match.len == 0 or etag.len == 0) {
return false;
}
// Parse comma-separated list of entity tags
var iter = std.mem.splitScalar(u8, if_none_match, ',');
while (iter.next()) |tag_str| {
const parsed = parseEntityTag(tag_str);
if (weakEntityTagMatch(our_parsed.tag, our_parsed.is_weak, parsed.tag, parsed.is_weak)) {
return true; // Condition is false, so we should return 304
}
if (!ETag.ifNoneMatch(etag, if_none_match)) {
return false;
}
return false; // Condition is true, continue with normal processing
// Return 304 Not Modified
req.setYield(false);
this.ref();
if (this.server) |server| {
server.onPendingRequest();
resp.timeout(server.config().idleTimeout);
}
this.doWriteStatus(304, resp);
this.doWriteHeaders(resp);
resp.endWithoutBody(resp.shouldCloseConnection());
this.onResponseComplete(resp);
return true;
}
const std = @import("std");
const bun = @import("bun");
const jsc = bun.jsc;
const Headers = bun.http.Headers;
const api = bun.schema.api;
const AnyServer = jsc.API.AnyServer;
const writeStatus = bun.api.server.writeStatus;
const AnyBlob = jsc.WebCore.Blob.Any;
const ETag = bun.http.ETag;
const Headers = bun.http.Headers;
const uws = bun.uws;
const AnyResponse = uws.AnyResponse;

View File

@@ -426,6 +426,17 @@ pub fn constructJSON(
did_succeed = true;
return bun.new(Response, response).toJS(globalThis);
}
fn validateRedirectStatusCode(globalThis: *jsc.JSGlobalObject, status_code: i32) bun.JSError!u16 {
switch (status_code) {
301, 302, 303, 307, 308 => return @intCast(status_code),
else => {
const err = globalThis.createRangeErrorInstance("Failed to execute 'redirect' on 'Response': Invalid status code", .{});
return globalThis.throwValue(err);
},
}
}
pub fn constructRedirect(
globalThis: *jsc.JSGlobalObject,
callframe: *jsc.CallFrame,
@@ -464,36 +475,17 @@ pub fn constructRedirect(
if (args.nextEat()) |init| {
if (init.isUndefinedOrNull()) {} else if (init.isNumber()) {
const status_number = init.toInt32();
// Validate redirect status codes (301, 302, 303, 307, 308)
if (status_number == 301 or status_number == 302 or status_number == 303 or status_number == 307 or status_number == 308) {
response.init.status_code = @as(u16, @intCast(status_number));
} else {
const err = globalThis.createRangeErrorInstance("Failed to execute 'redirect' on 'Response': Invalid status code", .{});
return globalThis.throwValue(err);
}
} else if (init.isObject()) {
// Only process object init values to prevent crash with non-object values
if (Response.Init.init(globalThis, init) catch |err|
if (err == error.JSError) return .zero else null) |_init|
{
// Validate that status code is a valid redirect status if provided
if (_init.status_code != 200) { // 200 is the default, so if it's changed, validate it
if (_init.status_code == 301 or _init.status_code == 302 or _init.status_code == 303 or _init.status_code == 307 or _init.status_code == 308) {
response.init = _init;
} else {
response.init.deinit(bun.default_allocator);
const err = globalThis.createRangeErrorInstance("Failed to execute 'redirect' on 'Response': Invalid status code", .{});
return globalThis.throwValue(err);
}
} else {
response.init = _init;
response.init.status_code = 302; // Default redirect status
}
response.init.status_code = try validateRedirectStatusCode(globalThis, init.toInt32());
} else if (try Response.Init.init(globalThis, init)) |_init| {
errdefer response.init.deinit(bun.default_allocator);
response.init = _init;
if (_init.status_code != 200) {
response.init.status_code = try validateRedirectStatusCode(globalThis, _init.status_code);
}
}
// Non-object, non-number init values are ignored (like strings, booleans, etc.)
}
if (globalThis.hasException()) {
return .zero;
}
@@ -642,7 +634,13 @@ pub const Init = struct {
if (!response_init.isCell())
return null;
if (response_init.jsType() == .DOMWrapper) {
const js_type = response_init.jsType();
if (!js_type.isObject()) {
return null;
}
if (js_type == .DOMWrapper) {
// fast path: it's a Request object or a Response object
// we can skip calling JS getters
if (response_init.asDirect(Request)) |req| {

View File

@@ -702,7 +702,7 @@ pub const Signal = struct {
pub fn HTTPServerWritable(comptime ssl: bool) type {
return struct {
const UWSResponse = uws.NewApp(ssl).Response;
res: *UWSResponse,
res: ?*UWSResponse,
buffer: bun.ByteList,
pooled_buffer: ?*WebCore.ByteListPool.Node = null,
offset: Blob.SizeType = 0,
@@ -774,22 +774,26 @@ pub fn HTTPServerWritable(comptime ssl: bool) type {
bun.assert(!this.done);
defer log("send: {d} bytes (backpressure: {any})", .{ buf.len, this.has_backpressure });
if (this.requested_end and !this.res.state().isHttpWriteCalled()) {
const res = this.res orelse {
return false;
};
if (this.requested_end and !res.state().isHttpWriteCalled()) {
this.handleFirstWriteIfNecessary();
const success = this.res.tryEnd(buf, this.end_len, false);
const success = res.tryEnd(buf, this.end_len, false);
if (success) {
this.has_backpressure = false;
this.handleWrote(this.end_len);
} else {
} else if (this.res != null) {
this.has_backpressure = true;
this.res.onWritable(*@This(), onWritable, this);
res.onWritable(*@This(), onWritable, this);
}
return success;
}
// clean this so we know when its relevant or not
this.end_len = 0;
// we clear the onWritable handler so uWS can handle the backpressure for us
this.res.clearOnWritable();
res.clearOnWritable();
this.handleFirstWriteIfNecessary();
// uWebSockets lacks a tryWrite() function
// This means that backpressure will be handled by appending to an "infinite" memory buffer
@@ -797,10 +801,10 @@ pub fn HTTPServerWritable(comptime ssl: bool) type {
// so in this scenario, we just append to the buffer
// and report success
if (this.requested_end) {
this.res.end(buf, false);
res.end(buf, false);
this.has_backpressure = false;
} else {
this.has_backpressure = this.res.write(buf) == .backpressure;
this.has_backpressure = res.write(buf) == .backpressure;
}
this.handleWrote(buf.len);
return true;
@@ -821,7 +825,6 @@ pub fn HTTPServerWritable(comptime ssl: bool) type {
// onWritable reset backpressure state to allow flushing
this.has_backpressure = false;
if (this.aborted) {
this.res.clearOnWritable();
this.signal.close(null);
this.flushPromise();
this.finalize();
@@ -837,7 +840,6 @@ pub fn HTTPServerWritable(comptime ssl: bool) type {
// if we have nothing to write, we are done
if (chunk.len == 0) {
if (this.done) {
this.res.clearOnWritable();
this.signal.close(null);
this.flushPromise();
this.finalize();
@@ -851,7 +853,9 @@ pub fn HTTPServerWritable(comptime ssl: bool) type {
total_written = chunk.len;
if (this.requested_end) {
this.res.clearOnWritable();
if (this.res) |res| {
res.clearOnWritable();
}
this.signal.close(null);
this.flushPromise();
this.finalize();
@@ -875,7 +879,7 @@ pub fn HTTPServerWritable(comptime ssl: bool) type {
}
pub fn start(this: *@This(), stream_start: Start) bun.sys.Maybe(void) {
if (this.aborted or this.res.hasResponded()) {
if (this.aborted or this.res == null or this.res.?.hasResponded()) {
this.markDone();
this.signal.close(null);
return .success;
@@ -985,7 +989,7 @@ pub fn HTTPServerWritable(comptime ssl: bool) type {
return .success;
}
if (this.res.hasResponded()) {
if (this.res == null or this.res.?.hasResponded()) {
this.markDone();
this.signal.close(null);
}
@@ -1040,7 +1044,7 @@ pub fn HTTPServerWritable(comptime ssl: bool) type {
return .{ .owned = 0 };
}
if (this.res.hasResponded()) {
if (this.res == null or this.res.?.hasResponded()) {
this.signal.close(null);
this.markDone();
return .{ .done = {} };
@@ -1098,7 +1102,7 @@ pub fn HTTPServerWritable(comptime ssl: bool) type {
return .{ .owned = 0 };
}
if (this.res.hasResponded()) {
if (this.res == null or this.res.?.hasResponded()) {
this.signal.close(null);
this.markDone();
return .{ .done = {} };
@@ -1138,7 +1142,7 @@ pub fn HTTPServerWritable(comptime ssl: bool) type {
return .success;
}
if (this.done or this.res.hasResponded()) {
if (this.done or this.res == null or this.res.?.hasResponded()) {
this.signal.close(err);
this.markDone();
this.finalize();
@@ -1167,7 +1171,7 @@ pub fn HTTPServerWritable(comptime ssl: bool) type {
return .{ .result = jsc.JSValue.jsNumber(0) };
}
if (this.done or this.res.hasResponded()) {
if (this.done or this.res == null or this.res.?.hasResponded()) {
this.requested_end = true;
this.signal.close(null);
this.markDone();
@@ -1188,7 +1192,9 @@ pub fn HTTPServerWritable(comptime ssl: bool) type {
return .{ .result = value };
}
} else {
this.res.end("", false);
if (this.res) |res| {
res.end("", false);
}
}
this.markDone();
@@ -1206,6 +1212,7 @@ pub fn HTTPServerWritable(comptime ssl: bool) type {
pub fn abort(this: *@This()) void {
log("onAborted()", .{});
this.done = true;
this.res = null;
this.unregisterAutoFlusher();
this.aborted = true;
@@ -1222,8 +1229,9 @@ pub fn HTTPServerWritable(comptime ssl: bool) type {
}
fn registerAutoFlusher(this: *@This()) void {
const res = this.res orelse return;
// if we enqueue data we should reset the timeout
this.res.resetTimeout();
res.resetTimeout();
if (!this.auto_flusher.registered)
AutoFlusher.registerDeferredMicrotaskWithTypeUnchecked(@This(), this, this.globalThis.bunVM());
}
@@ -1268,14 +1276,19 @@ pub fn HTTPServerWritable(comptime ssl: bool) type {
log("finalize()", .{});
if (!this.done) {
this.unregisterAutoFlusher();
// make sure we detached the handlers before flushing inside the finalize function
this.res.clearOnWritable();
this.res.clearAborted();
this.res.clearOnData();
if (this.res) |res| {
// make sure we detached the handlers before flushing inside the finalize function
res.clearOnWritable();
res.clearAborted();
res.clearOnData();
}
_ = this.flushNoWait();
this.done = true;
// is actually fine to call this if the socket is closed because of flushNoWait, the free will be defered by usockets
this.res.endStream(false);
if (this.res) |res| {
// is actually fine to call this if the socket is closed because of flushNoWait, the free will be defered by usockets
res.endStream(false);
}
}
if (comptime !FeatureFlags.http_buffer_pooling) {

View File

@@ -2424,6 +2424,7 @@ pub const ThreadlocalAsyncHTTP = struct {
async_http: AsyncHTTP,
};
pub const ETag = @import("./http/ETag.zig");
pub const Method = @import("./http/Method.zig").Method;
pub const Headers = @import("./http/Headers.zig");
pub const MimeType = @import("./http/MimeType.zig");

65
src/http/ETag.zig Normal file
View File

@@ -0,0 +1,65 @@
const ETag = @This();
/// Parse a single entity tag from a string, returns the tag without quotes and whether it's weak
fn parse(tag_str: []const u8) struct { tag: []const u8, is_weak: bool } {
var str = std.mem.trim(u8, tag_str, " \t");
// Check for weak indicator
var is_weak = false;
if (bun.strings.hasPrefix(str, "W/")) {
is_weak = true;
str = str[2..];
str = std.mem.trimLeft(u8, str, " \t");
}
// Remove surrounding quotes
if (str.len >= 2 and str[0] == '"' and str[str.len - 1] == '"') {
str = str[1 .. str.len - 1];
}
return .{ .tag = str, .is_weak = is_weak };
}
/// Perform weak comparison between two entity tags according to RFC 9110 Section 8.8.3.2
fn weakMatch(tag1: []const u8, is_weak1: bool, tag2: []const u8, is_weak2: bool) bool {
_ = is_weak1;
_ = is_weak2;
// For weak comparison, we only compare the opaque tag values, ignoring weak indicators
return std.mem.eql(u8, tag1, tag2);
}
pub fn appendToHeaders(bytes: []const u8, headers: *bun.http.Headers) !void {
const hash = std.hash.XxHash64.hash(0, bytes);
var etag_buf: [40]u8 = undefined;
const etag_str = std.fmt.bufPrint(&etag_buf, "\"{}\"", .{bun.fmt.hexIntLower(hash)}) catch unreachable;
try headers.append("etag", etag_str);
}
pub fn ifNoneMatch(
/// "ETag" header
etag: []const u8,
/// "If-None-Match" header
if_none_match: []const u8,
) bool {
const our_parsed = parse(etag);
// Handle "*" case
if (std.mem.eql(u8, std.mem.trim(u8, if_none_match, " \t"), "*")) {
return true; // Condition is false, so we should return 304
}
// Parse comma-separated list of entity tags
var iter = std.mem.splitScalar(u8, if_none_match, ',');
while (iter.next()) |tag_str| {
const parsed = parse(tag_str);
if (weakMatch(our_parsed.tag, our_parsed.is_weak, parsed.tag, parsed.is_weak)) {
return true; // Condition is false, so we should return 304
}
}
return false; // Condition is true, continue with normal processing
}
const bun = @import("bun");
const std = @import("std");

View File

@@ -52,6 +52,38 @@ pub fn detectAndLoadOtherLockfile(
return migrate_result;
}
yarn: {
var timer = std.time.Timer.start() catch unreachable;
const lockfile = File.openat(dir, "yarn.lock", bun.O.RDONLY, 0).unwrap() catch break :yarn;
defer lockfile.close();
const data = lockfile.readToEnd(allocator).unwrap() catch break :yarn;
const migrate_result = @import("./yarn.zig").migrateYarnLockfile(this, manager, allocator, log, data, dir) catch |err| {
if (Environment.isDebug) {
bun.handleErrorReturnTrace(err, @errorReturnTrace());
Output.prettyErrorln("Error: {s}", .{@errorName(err)});
log.print(Output.errorWriter()) catch {};
Output.prettyErrorln("Invalid yarn.lock\nIn a release build, this would ignore and do a fresh install.\nAborting", .{});
Global.exit(1);
}
return LoadResult{ .err = .{
.step = .migrating,
.value = err,
.lockfile_path = "yarn.lock",
.format = .binary,
} };
};
if (migrate_result == .ok) {
Output.printElapsed(@as(f64, @floatFromInt(timer.read())) / std.time.ns_per_ms);
Output.prettyError(" ", .{});
Output.prettyErrorln("<d>migrated lockfile from <r><green>yarn.lock<r>", .{});
Output.flush();
}
return migrate_result;
}
return LoadResult{ .not_found = {} };
}

1718
src/install/yarn.zig Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,2 @@
# we do need it the files here
!yarn.lock

View File

@@ -0,0 +1,157 @@
{
"name": "yarn",
"installationMethod": "unknown",
"version": "1.23.0-0",
"license": "BSD-2-Clause",
"preferGlobal": true,
"description": "📦🐈 Fast, reliable, and secure dependency management.",
"dependencies": {
"@zkochan/cmd-shim": "^3.1.0",
"babel-runtime": "^6.26.0",
"bytes": "^3.0.0",
"camelcase": "^4.0.0",
"chalk": "^2.1.0",
"cli-table3": "^0.4.0",
"commander": "^2.9.0",
"death": "^1.0.0",
"debug": "^3.0.0",
"deep-equal": "^1.0.1",
"detect-indent": "^5.0.0",
"dnscache": "^1.0.1",
"glob": "^7.1.1",
"gunzip-maybe": "^1.4.0",
"hash-for-dep": "^1.2.3",
"imports-loader": "^0.8.0",
"ini": "^1.3.4",
"inquirer": "^6.2.0",
"invariant": "^2.2.0",
"is-builtin-module": "^2.0.0",
"is-ci": "^1.0.10",
"is-webpack-bundle": "^1.0.0",
"js-yaml": "^3.13.1",
"leven": "^2.0.0",
"loud-rejection": "^1.2.0",
"micromatch": "^2.3.11",
"mkdirp": "^0.5.1",
"node-emoji": "^1.6.1",
"normalize-url": "^2.0.0",
"npm-logical-tree": "^1.2.1",
"object-path": "^0.11.2",
"proper-lockfile": "^2.0.0",
"puka": "^1.0.0",
"read": "^1.0.7",
"request": "^2.87.0",
"request-capture-har": "^1.2.2",
"rimraf": "^2.5.0",
"semver": "^5.1.0",
"ssri": "^5.3.0",
"strip-ansi": "^4.0.0",
"strip-bom": "^3.0.0",
"tar-fs": "^1.16.0",
"tar-stream": "^1.6.1",
"uuid": "^3.0.1",
"v8-compile-cache": "^2.0.0",
"validate-npm-package-license": "^3.0.4",
"yn": "^2.0.0"
},
"devDependencies": {
"babel-core": "^6.26.0",
"babel-eslint": "^7.2.3",
"babel-loader": "^6.2.5",
"babel-plugin-array-includes": "^2.0.3",
"babel-plugin-inline-import": "^3.0.0",
"babel-plugin-transform-builtin-extend": "^1.1.2",
"babel-plugin-transform-inline-imports-commonjs": "^1.0.0",
"babel-plugin-transform-runtime": "^6.4.3",
"babel-preset-env": "^1.6.0",
"babel-preset-flow": "^6.23.0",
"babel-preset-stage-0": "^6.0.0",
"babylon": "^6.5.0",
"commitizen": "^2.9.6",
"cz-conventional-changelog": "^2.0.0",
"eslint": "^4.3.0",
"eslint-config-fb-strict": "^22.0.0",
"eslint-plugin-babel": "^5.0.0",
"eslint-plugin-flowtype": "^2.35.0",
"eslint-plugin-jasmine": "^2.6.2",
"eslint-plugin-jest": "^21.0.0",
"eslint-plugin-jsx-a11y": "^6.0.2",
"eslint-plugin-prefer-object-spread": "^1.2.1",
"eslint-plugin-prettier": "^2.1.2",
"eslint-plugin-react": "^7.1.0",
"eslint-plugin-relay": "^0.0.28",
"eslint-plugin-yarn-internal": "file:scripts/eslint-rules",
"execa": "^0.11.0",
"fancy-log": "^1.3.2",
"flow-bin": "^0.66.0",
"git-release-notes": "^3.0.0",
"gulp": "^4.0.0",
"gulp-babel": "^7.0.0",
"gulp-if": "^2.0.1",
"gulp-newer": "^1.0.0",
"gulp-plumber": "^1.0.1",
"gulp-sourcemaps": "^2.2.0",
"jest": "^22.4.4",
"jsinspect": "^0.12.6",
"minimatch": "^3.0.4",
"mock-stdin": "^0.3.0",
"prettier": "^1.5.2",
"string-replace-loader": "^2.1.1",
"temp": "^0.8.3",
"webpack": "^2.1.0-beta.25",
"yargs": "^6.3.0"
},
"resolutions": {
"sshpk": "^1.14.2"
},
"engines": {
"node": ">=4.0.0"
},
"repository": "yarnpkg/yarn",
"bin": {
"yarn": "./bin/yarn.js",
"yarnpkg": "./bin/yarn.js"
},
"scripts": {
"build": "gulp build",
"build-bundle": "node ./scripts/build-webpack.js",
"build-chocolatey": "powershell ./scripts/build-chocolatey.ps1",
"build-deb": "./scripts/build-deb.sh",
"build-dist": "bash ./scripts/build-dist.sh",
"build-win-installer": "scripts\\build-windows-installer.bat",
"changelog": "git-release-notes $(git describe --tags --abbrev=0 $(git describe --tags --abbrev=0)^)..$(git describe --tags --abbrev=0) scripts/changelog.md",
"dupe-check": "yarn jsinspect ./src",
"lint": "eslint . && flow check",
"pkg-tests": "yarn --cwd packages/pkg-tests jest yarn.test.js",
"prettier": "eslint src __tests__ --fix",
"release-branch": "./scripts/release-branch.sh",
"test": "yarn lint && yarn test-only",
"test-only": "node --max_old_space_size=4096 node_modules/jest/bin/jest.js --verbose",
"test-only-debug": "node --inspect-brk --max_old_space_size=4096 node_modules/jest/bin/jest.js --runInBand --verbose",
"test-coverage": "node --max_old_space_size=4096 node_modules/jest/bin/jest.js --coverage --verbose",
"watch": "gulp watch",
"commit": "git-cz"
},
"jest": {
"collectCoverageFrom": [
"src/**/*.js"
],
"testEnvironment": "node",
"modulePathIgnorePatterns": [
"__tests__/fixtures/",
"packages/pkg-tests/pkg-tests-fixtures",
"dist/"
],
"testPathIgnorePatterns": [
"__tests__/(fixtures|__mocks__)/",
"updates/",
"_(temp|mock|install|init|helpers).js$",
"packages/pkg-tests"
]
},
"config": {
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1 @@
bun.*

View File

@@ -0,0 +1,44 @@
{
"name": "mkdirp",
"description": "Recursively mkdir, like `mkdir -p`",
"version": "1.0.2",
"main": "index.js",
"keywords": [
"mkdir",
"directory",
"make dir",
"make",
"dir",
"recursive",
"native"
],
"repository": {
"type": "git",
"url": "https://github.com/isaacs/node-mkdirp.git"
},
"scripts": {
"test": "tap",
"snap": "tap",
"preversion": "npm test",
"postversion": "npm publish",
"postpublish": "git push origin --follow-tags"
},
"tap": {
"check-coverage": true,
"coverage-map": "map.js"
},
"devDependencies": {
"require-inject": "^1.4.4",
"tap": "^14.10.6"
},
"bin": "bin/cmd.js",
"license": "MIT",
"engines": {
"node": ">=10"
},
"files": [
"bin",
"lib",
"index.js"
]
}

View File

@@ -0,0 +1,5 @@
{
"dependencies": {
"mkdirp": "file:mkdirp"
}
}

View File

@@ -0,0 +1,6 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"mkdirp@file:mkdirp":
version "1.0.2"

View File

@@ -0,0 +1 @@
bun.*

View File

@@ -0,0 +1,5 @@
{
"dependencies": {
"mkdirp": "^1.0.2"
}
}

View File

@@ -0,0 +1,8 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
mkdirp@^1.0.2:
version "1.0.4"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==

View File

@@ -0,0 +1 @@
bun.*

View File

@@ -0,0 +1,5 @@
{
"dependencies": {
"mkdirp": "^1.0.2"
}
}

View File

@@ -0,0 +1,8 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
mkdirp@^1.0.2:
version "1.0.4"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==

View File

@@ -0,0 +1 @@
bun.*

View File

@@ -0,0 +1 @@
{"name":"abbrev","version":"1.1.1"}

View File

@@ -0,0 +1,4 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1

View File

@@ -0,0 +1,15 @@
{
"name": "a",
"version": "1.2.3",
"dependencies": {
"abbrev": "^1.1.1",
"full-git-url": "git+https://github.com/isaacs/abbrev-js.git",
"ghshort": "github:isaacs/abbrev-js",
"old": "npm:abbrev@1.0.x",
"pinned": "npm:abbrev@1.1.1",
"reg": "npm:abbrev@^1.1.1",
"remote": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
"symlink": "file:./abbrev-link-target",
"tarball": "file:abbrev-1.1.1.tgz"
}
}

View File

@@ -0,0 +1,42 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
abbrev@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==
"full-git-url@git+https://github.com/isaacs/abbrev-js.git":
version "3.0.1"
resolved "git+https://github.com/isaacs/abbrev-js.git#3f9802e56ff878761a338e43ecacbfed39d2181d"
"ghshort@github:isaacs/abbrev-js":
version "3.0.1"
resolved "https://codeload.github.com/isaacs/abbrev-js/tar.gz/3f9802e56ff878761a338e43ecacbfed39d2181d"
"old@npm:abbrev@1.0.x":
version "1.0.9"
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.0.9.tgz#91b4792588a7738c25f35dd6f63752a2f8776135"
integrity sha512-LEyx4aLEC3x6T0UguF6YILf+ntvmOaWsVfENmIW0E9H09vKlLDGelMjjSm0jkDHALj8A8quZ/HapKNigzwge+Q==
"pinned@npm:abbrev@1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==
"reg@npm:abbrev@^1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==
"remote@https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz":
version "1.1.1"
resolved "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
"symlink@file:./abbrev-link-target":
version "1.1.1"
"tarball@file:abbrev-1.1.1.tgz":
version "1.1.1"
resolved "file:abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"