diff --git a/src/install/bun.lock.zig b/src/install/bun.lock.zig index 1ee9e33879599a..bf22bc43483eb6 100644 --- a/src/install/bun.lock.zig +++ b/src/install/bun.lock.zig @@ -436,6 +436,9 @@ pub const Stringifier = struct { defer found_overrides.deinit(allocator); try found_overrides.ensureTotalCapacity(allocator, @truncate(lockfile.overrides.map.count())); + var optional_peers_buf = std.ArrayList(String).init(allocator); + defer optional_peers_buf.deinit(); + var _indent: u32 = 0; const indent = &_indent; try writer.writeAll("{\n"); @@ -458,6 +461,7 @@ pub const Stringifier = struct { buf, deps_buf, lockfile.workspace_versions, + &optional_peers_buf, ); for (0..pkgs.len) |pkg_id| { const res = pkg_resolution[pkg_id]; @@ -475,6 +479,7 @@ pub const Stringifier = struct { buf, deps_buf, lockfile.workspace_versions, + &optional_peers_buf, ); } } @@ -608,11 +613,38 @@ pub const Stringifier = struct { ); } + const DepSortCtx = struct { + string_buf: string, + deps_buf: []const Dependency, + + pub fn isLessThan(this: @This(), lhs: DependencyID, rhs: DependencyID) bool { + const l = this.deps_buf[lhs]; + const r = this.deps_buf[rhs]; + return strings.cmpStringsAsc({}, l.name.slice(this.string_buf), r.name.slice(this.string_buf)); + } + }; + + var deps_sort_buf: std.ArrayListUnmanaged(DependencyID) = .{}; + defer deps_sort_buf.deinit(allocator); + + var pkg_deps_sort_buf: std.ArrayListUnmanaged(DependencyID) = .{}; + defer pkg_deps_sort_buf.deinit(allocator); + try writeIndent(writer, indent); try writer.writeAll("\"packages\": {"); var first = true; while (pkgs_iter.next({})) |node| { - for (node.dependencies) |dep_id| { + deps_sort_buf.clearRetainingCapacity(); + try deps_sort_buf.appendSlice(allocator, node.dependencies); + + std.sort.pdq( + DependencyID, + deps_sort_buf.items, + DepSortCtx{ .string_buf = buf, .deps_buf = deps_buf }, + DepSortCtx.isLessThan, + ); + + for (deps_sort_buf.items) |dep_id| { const pkg_id = resolution_buf[dep_id]; if (pkg_id == invalid_package_id) continue; @@ -651,7 +683,20 @@ pub const Stringifier = struct { const pkg_name = pkg_names[pkg_id].slice(buf); const pkg_meta = pkg_metas[pkg_id]; - const pkg_deps = pkg_dep_lists[pkg_id].get(deps_buf); + const pkg_deps_list = pkg_dep_lists[pkg_id]; + + pkg_deps_sort_buf.clearRetainingCapacity(); + try pkg_deps_sort_buf.ensureUnusedCapacity(allocator, pkg_deps_list.len); + for (pkg_deps_list.begin()..pkg_deps_list.end()) |pkg_dep_id| { + pkg_deps_sort_buf.appendAssumeCapacity(@intCast(pkg_dep_id)); + } + + std.sort.pdq( + DependencyID, + pkg_deps_sort_buf.items, + DepSortCtx{ .string_buf = buf, .deps_buf = deps_buf }, + DepSortCtx.isLessThan, + ); // first index is resolution for all dependency types // npm -> [ "name@version", registry or "" (default), deps..., integrity, ... ] @@ -663,9 +708,6 @@ pub const Stringifier = struct { // git -> [ "name@git+repo", deps..., ... ] // github -> [ "name@github:user/repo", deps..., ... ] - var optional_peers_buf = std.ArrayList(String).init(allocator); - defer optional_peers_buf.deinit(); - switch (res.tag) { .root => { try writer.print("[\"{}@root:\"]", .{ @@ -679,7 +721,7 @@ pub const Stringifier = struct { bun.fmt.formatJSONStringUTF8(res.value.folder.slice(buf), .{ .quote = false }), }); - try writePackageDepsAndMeta(writer, pkg_deps, &pkg_meta, buf, &optional_peers_buf); + try writePackageDepsAndMeta(writer, dep_id, deps_buf, pkg_deps_sort_buf.items, &pkg_meta, buf, &optional_peers_buf); try writer.print(", \"{}\"]", .{pkg_meta.integrity}); }, @@ -689,7 +731,7 @@ pub const Stringifier = struct { bun.fmt.formatJSONStringUTF8(res.value.local_tarball.slice(buf), .{ .quote = false }), }); - try writePackageDepsAndMeta(writer, pkg_deps, &pkg_meta, buf, &optional_peers_buf); + try writePackageDepsAndMeta(writer, dep_id, deps_buf, pkg_deps_sort_buf.items, &pkg_meta, buf, &optional_peers_buf); try writer.print(", \"{}\"]", .{pkg_meta.integrity}); }, @@ -699,7 +741,7 @@ pub const Stringifier = struct { bun.fmt.formatJSONStringUTF8(res.value.remote_tarball.slice(buf), .{ .quote = false }), }); - try writePackageDepsAndMeta(writer, pkg_deps, &pkg_meta, buf, &optional_peers_buf); + try writePackageDepsAndMeta(writer, dep_id, deps_buf, pkg_deps_sort_buf.items, &pkg_meta, buf, &optional_peers_buf); try writer.print(", \"{}\"]", .{pkg_meta.integrity}); }, @@ -709,7 +751,7 @@ pub const Stringifier = struct { bun.fmt.formatJSONStringUTF8(res.value.symlink.slice(buf), .{ .quote = false }), }); - try writePackageDepsAndMeta(writer, pkg_deps, &pkg_meta, buf, &optional_peers_buf); + try writePackageDepsAndMeta(writer, dep_id, deps_buf, pkg_deps_sort_buf.items, &pkg_meta, buf, &optional_peers_buf); try writer.print(", \"{}\"]", .{pkg_meta.integrity}); }, @@ -727,7 +769,7 @@ pub const Stringifier = struct { res.value.npm.url.slice(buf), }); - try writePackageDepsAndMeta(writer, pkg_deps, &pkg_meta, buf, &optional_peers_buf); + try writePackageDepsAndMeta(writer, dep_id, deps_buf, pkg_deps_sort_buf.items, &pkg_meta, buf, &optional_peers_buf); // TODO(dylan-conway): delete placeholder try writer.print(", \"{}\"]", .{ @@ -742,7 +784,7 @@ pub const Stringifier = struct { bun.fmt.formatJSONStringUTF8(workspace_path, .{ .quote = false }), }); - try writePackageDepsAndMeta(writer, pkg_deps, &pkg_meta, buf, &optional_peers_buf); + try writePackageDepsAndMeta(writer, dep_id, deps_buf, pkg_deps_sort_buf.items, &pkg_meta, buf, &optional_peers_buf); try writer.writeByte(']'); }, @@ -753,7 +795,7 @@ pub const Stringifier = struct { repo.fmt(if (comptime tag == .git) "git+" else "github:", buf), }); - try writePackageDepsAndMeta(writer, pkg_deps, &pkg_meta, buf, &optional_peers_buf); + try writePackageDepsAndMeta(writer, dep_id, deps_buf, pkg_deps_sort_buf.items, &pkg_meta, buf, &optional_peers_buf); try writer.print(", \"{}\"]", .{pkg_meta.integrity}); }, @@ -763,7 +805,7 @@ pub const Stringifier = struct { } if (!first) { - try writer.writeByte('\n'); + try writer.writeAll(",\n"); try decIndent(writer, indent); } try writer.writeAll("}\n"); @@ -779,7 +821,9 @@ pub const Stringifier = struct { /// { "devDependencies": { "one": "1.1.1", "two": "2.2.2" }, "os": "none" } fn writePackageDepsAndMeta( writer: anytype, - deps: []const Dependency, + _: DependencyID, + deps_buf: []const Dependency, + pkg_dep_ids: []const DependencyID, meta: *const Meta, buf: string, optional_peers_buf: *std.ArrayList(String), @@ -793,7 +837,8 @@ pub const Stringifier = struct { const group_name, const group_behavior = group; var first = true; - for (deps) |dep| { + for (pkg_dep_ids) |dep_id| { + const dep = deps_buf[dep_id]; if (!dep.behavior.includes(group_behavior)) continue; if (dep.behavior.isOptionalPeer()) { @@ -828,7 +873,7 @@ pub const Stringifier = struct { if (optional_peers_buf.items.len > 0) { bun.debugAssert(any); try writer.writeAll( - \\, "optionalPeers": [ + \\, "optionalPeerDependencies": [ ); for (optional_peers_buf.items, 0..) |optional_peer, i| { @@ -895,7 +940,9 @@ pub const Stringifier = struct { buf: string, deps_buf: []const Dependency, workspace_versions: BinaryLockfile.VersionHashMap, + optional_peers_buf: *std.ArrayList(String), ) OOM!void { + defer optional_peers_buf.clearRetainingCapacity(); // any - have any properties been written var any = false; @@ -931,6 +978,12 @@ pub const Stringifier = struct { for (pkg_deps[pkg_id].get(deps_buf)) |dep| { if (!dep.behavior.includes(group_behavior)) continue; + if (dep.behavior.isOptionalPeer()) { + if (group_behavior.isOptional()) continue; + + try optional_peers_buf.append(dep.name); + } + if (first) { if (any) { try writer.writeByte(','); @@ -957,16 +1010,39 @@ pub const Stringifier = struct { } if (!first) { - try writer.writeByte('\n'); + try writer.writeAll(",\n"); try decIndent(writer, indent); try writer.writeAll("}"); } } + if (optional_peers_buf.items.len > 0) { + bun.debugAssert(any); + try writer.writeAll( + \\, + \\ + ); + try writeIndent(writer, indent); + try writer.writeAll( + \\"optionalPeerDependencies": [ + \\ + ); + indent.* += 1; + for (optional_peers_buf.items) |optional_peer| { + try writeIndent(writer, indent); + try writer.print( + \\"{s}", + \\ + , .{optional_peer.slice(buf)}); + } + try decIndent(writer, indent); + try writer.writeByte(']'); + } + if (any) { - try writer.writeByte('\n'); + try writer.writeAll(",\n"); try decIndent(writer, indent); } - try writer.writeAll("}"); + try writer.writeAll("},"); } fn writeIndent(writer: anytype, indent: *const u32) OOM!void { @@ -999,8 +1075,8 @@ const dependency_groups = [3]struct { []const u8, Dependency.Behavior }{ const workspace_dependency_groups = [4]struct { []const u8, Dependency.Behavior }{ .{ "dependencies", Dependency.Behavior.normal }, .{ "devDependencies", Dependency.Behavior.dev }, - .{ "peerDependencies", Dependency.Behavior.peer }, .{ "optionalDependencies", Dependency.Behavior.optional }, + .{ "peerDependencies", Dependency.Behavior.peer }, }; const ParseError = OOM || error{ @@ -1424,7 +1500,7 @@ pub fn parseIntoBinaryLockfile( _ = pkg_resolution_lists; { - // root pkg + // first the root dependencies are resolved pkg_resolutions[0] = Resolution.init(.{ .root = {} }); pkg_metas[0].origin = .local; @@ -1437,8 +1513,12 @@ pub fn parseIntoBinaryLockfile( lockfile.buffers.resolutions.items[dep_id] = dep_node.pkg_id; } } + + // TODO(dylan-conway) should we handle workspaces separately here for custom hoisting + } + // then each package dependency for (pkgs_expr.data.e_object.properties.slice()) |prop| { const key = prop.key.?; const value = prop.value.?; @@ -1463,9 +1543,6 @@ pub fn parseIntoBinaryLockfile( var curr: ?*PkgPath.Map.Node = pkg_map_entry; while (curr) |node| { if (node.nodes.getPtr(dep.name.slice(string_buf.bytes.items))) |dep_node| { - - // it doesn't matter which dependency is assigned to this node. the dependency - // id will only be used for getting the dependency name dep_node.dep_id = dep_id; lockfile.buffers.resolutions.items[dep_id] = dep_node.pkg_id; @@ -1519,7 +1596,7 @@ fn parseAppendDependencies( ) ParseError!struct { u32, u32 } { defer optional_peers_buf.clearRetainingCapacity(); - if (obj.get("optionalPeers")) |optional_peers| { + if (obj.get("optionalPeerDependencies")) |optional_peers| { if (!optional_peers.isArray()) { try log.addError(source, optional_peers.loc, "Expected an array"); return error.InvalidPackageInfo; @@ -1592,5 +1669,12 @@ fn parseAppendDependencies( } const end = lockfile.buffers.dependencies.items.len; + std.sort.pdq( + Dependency, + lockfile.buffers.dependencies.items[off..], + buf.bytes.items, + Dependency.isLessThan, + ); + return .{ @intCast(off), @intCast(end - off) }; } diff --git a/src/install/lockfile.zig b/src/install/lockfile.zig index eeb196159f6660..7f3bab7458be63 100644 --- a/src/install/lockfile.zig +++ b/src/install/lockfile.zig @@ -425,7 +425,7 @@ pub const Tree = struct { id: Id = invalid_id, // Should not be used for anything other than name - // through `folderName()`. There is not guarentee a dependency + // through `folderName()`. There is not guarantee a dependency // id chosen for a tree node is the same behavior or has the // same version literal for packages hoisted. dependency_id: DependencyID = invalid_dependency_id,