MIRROR: javascript for 馃悳's, a tiny runtime with big ambitions
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

at master 1907 lines 66 kB view raw
1const std = @import("std"); 2const builtin = @import("builtin"); 3const lockfile = @import("lockfile.zig"); 4const intern = @import("intern.zig"); 5const fetcher = @import("fetcher.zig"); 6const json = @import("json.zig"); 7const debug = @import("debug.zig"); 8const cache = @import("cache.zig"); 9 10pub const ResolveError = error{ 11 InvalidPackageJson, 12 NetworkError, 13 NoMatchingVersion, 14 CyclicDependency, 15 OutOfMemory, 16 ParseError, 17 IoError, 18}; 19 20pub const Version = struct { 21 major: u64, 22 minor: u64, 23 patch: u64, 24 prerelease: ?[]const u8, 25 build: ?[]const u8, 26 27 pub fn parse(str: []const u8) !Version { 28 var remaining = str; 29 30 if (remaining.len > 0 and remaining[0] == 'v') { 31 remaining = remaining[1..]; 32 } 33 34 const major_end = std.mem.indexOfScalar(u8, remaining, '.') orelse return error.InvalidVersion; 35 const major = try std.fmt.parseInt(u64, remaining[0..major_end], 10); 36 remaining = remaining[major_end + 1 ..]; 37 38 const minor_end = std.mem.indexOfScalar(u8, remaining, '.') orelse return error.InvalidVersion; 39 const minor = try std.fmt.parseInt(u64, remaining[0..minor_end], 10); 40 remaining = remaining[minor_end + 1 ..]; 41 42 var patch_end = remaining.len; 43 var prerelease: ?[]const u8 = null; 44 var build: ?[]const u8 = null; 45 46 if (std.mem.indexOfScalar(u8, remaining, '-')) |dash| { 47 patch_end = dash; 48 const after_patch = remaining[dash + 1 ..]; 49 if (std.mem.indexOfScalar(u8, after_patch, '+')) |plus| { 50 prerelease = after_patch[0..plus]; 51 build = after_patch[plus + 1 ..]; 52 } else prerelease = after_patch; 53 } else if (std.mem.indexOfScalar(u8, remaining, '+')) |plus| { 54 patch_end = plus; 55 build = remaining[plus + 1 ..]; 56 } 57 58 const patch = try std.fmt.parseInt(u64, remaining[0..patch_end], 10); 59 60 return .{ 61 .major = major, 62 .minor = minor, 63 .patch = patch, 64 .prerelease = prerelease, 65 .build = build, 66 }; 67 } 68 69 pub fn order(a: Version, b: Version) std.math.Order { 70 if (a.major != b.major) return std.math.order(a.major, b.major); 71 if (a.minor != b.minor) return std.math.order(a.minor, b.minor); 72 if (a.patch != b.patch) return std.math.order(a.patch, b.patch); 73 74 if (a.prerelease == null and b.prerelease != null) return .gt; 75 if (a.prerelease != null and b.prerelease == null) return .lt; 76 if (a.prerelease == null and b.prerelease == null) return .eq; 77 78 return orderPrerelease(a.prerelease.?, b.prerelease.?); 79 } 80 81 fn orderPrerelease(a: []const u8, b: []const u8) std.math.Order { 82 var a_rest: []const u8 = a; 83 var b_rest: []const u8 = b; 84 85 while (true) { 86 const a_end = std.mem.indexOfScalar(u8, a_rest, '.') orelse a_rest.len; 87 const b_end = std.mem.indexOfScalar(u8, b_rest, '.') orelse b_rest.len; 88 89 const a_id = a_rest[0..a_end]; 90 const b_id = b_rest[0..b_end]; 91 92 const cmp = compareIdentifier(a_id, b_id); 93 if (cmp != .eq) return cmp; 94 95 const a_done = a_end >= a_rest.len; 96 const b_done = b_end >= b_rest.len; 97 if (a_done and b_done) return .eq; 98 if (a_done) return .lt; 99 if (b_done) return .gt; 100 101 a_rest = a_rest[a_end + 1 ..]; 102 b_rest = b_rest[b_end + 1 ..]; 103 } 104 } 105 106 fn compareIdentifier(a: []const u8, b: []const u8) std.math.Order { 107 const a_num = parseNumeric(a); 108 const b_num = parseNumeric(b); 109 110 if (a_num != null and b_num != null) { 111 return std.math.order(a_num.?, b_num.?); 112 } 113 114 if (a_num != null) return .lt; 115 if (b_num != null) return .gt; 116 117 return std.mem.order(u8, a, b); 118 } 119 120 fn parseNumeric(s: []const u8) ?u64 { 121 if (s.len == 0) return null; 122 var val: u64 = 0; 123 for (s) |c| { 124 if (c < '0' or c > '9') return null; 125 val = val * 10 + (c - '0'); 126 } 127 return val; 128 } 129 130 pub fn format(self: Version, allocator: std.mem.Allocator) ![]u8 { 131 if (self.prerelease) |pre| { 132 return std.fmt.allocPrint(allocator, "{d}.{d}.{d}-{s}", .{ 133 self.major, self.minor, self.patch, pre, 134 }); 135 } 136 return std.fmt.allocPrint(allocator, "{d}.{d}.{d}", .{ 137 self.major, self.minor, self.patch, 138 }); 139 } 140}; 141 142pub const Constraint = struct { 143 kind: Kind, 144 version: Version, 145 146 pub const Kind = enum { 147 exact, // 1.2.3 148 caret, // ^1.2.3 (>=1.2.3 <2.0.0) 149 tilde, // ~1.2.3 (>=1.2.3 <1.3.0) 150 gte, // >=1.2.3 151 gt, // >1.2.3 152 lte, // <=1.2.3 153 lt, // <1.2.3 154 any, // * 155 }; 156 157 pub fn parse(str: []const u8) !Constraint { 158 if (str.len == 0 or std.mem.eql(u8, str, "*") or std.mem.eql(u8, str, "latest")) { 159 return .{ .kind = .any, .version = .{ .major = 0, .minor = 0, .patch = 0, .prerelease = null, .build = null } }; 160 } 161 162 var remaining = str; 163 var kind: Kind = .exact; 164 165 if (std.mem.lastIndexOf(u8, remaining, "||")) |or_idx| { 166 remaining = std.mem.trim(u8, remaining[or_idx + 2 ..], " "); 167 } 168 169 if (std.mem.indexOf(u8, remaining, " ")) |space| { 170 remaining = remaining[0..space]; 171 } 172 173 if (std.mem.startsWith(u8, remaining, "^")) { 174 kind = .caret; 175 remaining = remaining[1..]; 176 } else if (std.mem.startsWith(u8, remaining, "~")) { 177 kind = .tilde; 178 remaining = remaining[1..]; 179 } else if (std.mem.startsWith(u8, remaining, ">=")) { 180 kind = .gte; 181 remaining = remaining[2..]; 182 } else if (std.mem.startsWith(u8, remaining, ">")) { 183 kind = .gt; 184 remaining = remaining[1..]; 185 } else if (std.mem.startsWith(u8, remaining, "<=")) { 186 kind = .lte; 187 remaining = remaining[2..]; 188 } else if (std.mem.startsWith(u8, remaining, "<")) { 189 kind = .lt; 190 remaining = remaining[1..]; 191 } else if (std.mem.startsWith(u8, remaining, "=")) { 192 remaining = remaining[1..]; 193 } 194 195 const dot_count = std.mem.count(u8, remaining, "."); 196 if (dot_count == 0) { 197 const major = std.fmt.parseInt(u64, remaining, 10) catch return .{ 198 .kind = .any, 199 .version = .{ .major = 0, .minor = 0, .patch = 0, .prerelease = null, .build = null }, 200 }; 201 return .{ 202 .kind = if (kind == .exact) .caret else kind, 203 .version = .{ .major = major, .minor = 0, .patch = 0, .prerelease = null, .build = null }, 204 }; 205 } else if (dot_count == 1) { 206 var parts = std.mem.splitScalar(u8, remaining, '.'); 207 const major = std.fmt.parseInt(u64, parts.next().?, 10) catch 0; 208 const minor = std.fmt.parseInt(u64, parts.next().?, 10) catch 0; 209 return .{ 210 .kind = if (kind == .exact) .tilde else kind, 211 .version = .{ .major = major, .minor = minor, .patch = 0, .prerelease = null, .build = null }, 212 }; 213 } 214 215 const version = try Version.parse(remaining); 216 return .{ .kind = kind, .version = version }; 217 } 218 219 pub fn satisfies(self: Constraint, v: Version) bool { 220 switch (self.kind) { 221 .any => return true, 222 .exact => { 223 if (v.major != self.version.major or v.minor != self.version.minor or v.patch != self.version.patch) return false; 224 if (self.version.prerelease == null and v.prerelease == null) return true; 225 if (self.version.prerelease == null or v.prerelease == null) return false; 226 return std.mem.eql(u8, self.version.prerelease.?, v.prerelease.?); 227 }, 228 .caret => { 229 // ^1.2.3 means >=1.2.3 <2.0.0 (for major > 0) 230 // ^0.2.3 means >=0.2.3 <0.3.0 (for major = 0) 231 // ^0.0.3 means >=0.0.3 <0.0.4 (for major = 0, minor = 0) 232 if (v.order(self.version) == .lt) return false; 233 if (self.version.major > 0) { 234 return v.major == self.version.major; 235 } else if (self.version.minor > 0) { 236 return v.major == 0 and v.minor == self.version.minor; 237 } else { 238 return v.major == 0 and v.minor == 0 and v.patch == self.version.patch; 239 } 240 }, 241 .tilde => { 242 // ~1.2.3 means >=1.2.3 <1.3.0 243 if (v.order(self.version) == .lt) return false; 244 return v.major == self.version.major and v.minor == self.version.minor; 245 }, 246 .gte => return v.order(self.version) != .lt, 247 .gt => return v.order(self.version) == .gt, 248 .lte => return v.order(self.version) != .gt, 249 .lt => return v.order(self.version) == .lt, 250 } 251 } 252}; 253 254const DependencySpec = struct { 255 install_name: []const u8, 256 package_name: []const u8, 257 constraint: []const u8, 258}; 259 260const NpmAliasSpec = struct { 261 package_name: []const u8, 262 constraint: []const u8, 263}; 264 265fn parseNpmAliasSpec(spec: []const u8) ?NpmAliasSpec { 266 if (!std.mem.startsWith(u8, spec, "npm:")) return null; 267 268 const rest = spec["npm:".len..]; 269 if (rest.len == 0) return null; 270 271 const version_at: ?usize = if (rest[0] == '@') blk: { 272 const slash = std.mem.indexOfScalar(u8, rest, '/') orelse return null; 273 if (slash + 1 >= rest.len) return null; 274 if (std.mem.indexOfScalar(u8, rest[slash + 1 ..], '@')) |rel_at| { 275 break :blk slash + 1 + rel_at; 276 } 277 break :blk null; 278 } else std.mem.indexOfScalar(u8, rest, '@'); 279 280 const package_name = if (version_at) |at| rest[0..at] else rest; 281 if (package_name.len == 0) return null; 282 283 const constraint = if (version_at) |at| blk: { 284 const alias_constraint = rest[at + 1 ..]; 285 break :blk if (alias_constraint.len > 0) alias_constraint else "latest"; 286 } else "latest"; 287 288 return .{ .package_name = package_name, .constraint = constraint }; 289} 290 291fn dependencySpec(install_name: []const u8, constraint: []const u8) DependencySpec { 292 if (parseNpmAliasSpec(constraint)) |alias| { 293 return .{ 294 .install_name = install_name, 295 .package_name = alias.package_name, 296 .constraint = alias.constraint, 297 }; 298 } 299 return .{ 300 .install_name = install_name, 301 .package_name = install_name, 302 .constraint = constraint, 303 }; 304} 305 306pub const VersionInfo = struct { 307 version: Version, 308 version_str: []const u8, 309 integrity: [64]u8, 310 tarball_url: []const u8, 311 dependencies: std.StringHashMap([]const u8), 312 optional_dependencies: std.StringHashMap([]const u8), 313 peer_dependencies: std.StringHashMap([]const u8), 314 peer_dependencies_meta: std.StringHashMap(bool), 315 os: ?[]const u8, 316 cpu: ?[]const u8, 317 libc: ?[]const u8, 318 bin: std.StringHashMap([]const u8), 319 allocator: std.mem.Allocator, 320 321 pub fn deinit(self: *VersionInfo) void { 322 self.allocator.free(self.version_str); 323 self.allocator.free(self.tarball_url); 324 if (self.version.prerelease) |pre| self.allocator.free(pre); 325 if (self.version.build) |bld| self.allocator.free(bld); 326 var iter = self.dependencies.iterator(); 327 while (iter.next()) |entry| { 328 self.allocator.free(entry.key_ptr.*); 329 self.allocator.free(entry.value_ptr.*); 330 } 331 self.dependencies.deinit(); 332 var opt_iter = self.optional_dependencies.iterator(); 333 while (opt_iter.next()) |entry| { 334 self.allocator.free(entry.key_ptr.*); 335 self.allocator.free(entry.value_ptr.*); 336 } 337 self.optional_dependencies.deinit(); 338 var peer_iter = self.peer_dependencies.iterator(); 339 while (peer_iter.next()) |entry| { 340 self.allocator.free(entry.key_ptr.*); 341 self.allocator.free(entry.value_ptr.*); 342 } 343 self.peer_dependencies.deinit(); 344 var peer_meta_iter = self.peer_dependencies_meta.iterator(); 345 while (peer_meta_iter.next()) |entry| { 346 self.allocator.free(entry.key_ptr.*); 347 } 348 self.peer_dependencies_meta.deinit(); 349 if (self.os) |o| self.allocator.free(o); 350 if (self.cpu) |c| self.allocator.free(c); 351 if (self.libc) |l| self.allocator.free(l); 352 var bin_iter = self.bin.iterator(); 353 while (bin_iter.next()) |entry| { 354 self.allocator.free(entry.key_ptr.*); 355 self.allocator.free(entry.value_ptr.*); 356 } 357 self.bin.deinit(); 358 } 359 360 pub fn matchesPlatform(self: *const VersionInfo) bool { 361 const current_os = comptime switch (builtin.os.tag) { 362 .macos => "darwin", 363 .linux => "linux", 364 .windows => "win32", 365 .freebsd => "freebsd", 366 else => "unknown", 367 }; 368 369 const current_cpu = comptime switch (builtin.cpu.arch) { 370 .aarch64 => "arm64", 371 .x86_64 => "x64", 372 .x86 => "ia32", 373 .arm => "arm", 374 else => "unknown", 375 }; 376 377 const current_libc: ?[]const u8 = comptime if (builtin.os.tag != .linux) null else if (builtin.abi == .gnu or builtin.abi == .gnueabi or builtin.abi == .gnueabihf) "glibc" else if (builtin.abi == .musl or builtin.abi == .musleabi or builtin.abi == .musleabihf) "musl" else null; 378 379 if (self.os) |os_filter| if (!matchesFilter(os_filter, current_os)) return false; 380 if (self.cpu) |cpu_filter| if (!matchesFilter(cpu_filter, current_cpu)) return false; 381 382 if (self.libc) |libc_filter| { 383 if (current_libc) |libc| if (!matchesFilter(libc_filter, libc)) return false; 384 } 385 386 return true; 387 } 388 389 fn matchesFilter(filter: []const u8, value: []const u8) bool { 390 var has_positive = false; 391 var matches = false; 392 393 var iter = std.mem.splitScalar(u8, filter, ','); 394 while (iter.next()) |part| { 395 const trimmed = std.mem.trim(u8, part, " "); 396 if (trimmed.len == 0) continue; 397 398 if (trimmed[0] == '!') { 399 if (std.mem.eql(u8, trimmed[1..], value)) return false; 400 } else { 401 has_positive = true; 402 if (std.mem.eql(u8, trimmed, value)) matches = true; 403 } 404 } 405 406 return if (has_positive) matches else true; 407 } 408}; 409 410fn parseDepsMap( 411 allocator: std.mem.Allocator, 412 maybe_obj: ?std.json.Value, 413) std.StringHashMap([]const u8) { 414 var map = std.StringHashMap([]const u8).init(allocator); 415 416 const deps_obj = maybe_obj orelse return map; 417 if (deps_obj != .object) return map; 418 419 for (deps_obj.object.keys(), deps_obj.object.values()) |dep_name, dep_ver| { 420 if (dep_ver != .string) continue; 421 422 const key = allocator.dupe(u8, dep_name) catch continue; 423 const val = allocator.dupe(u8, dep_ver.string) catch { 424 allocator.free(key); 425 continue; 426 }; 427 428 map.put(key, val) catch { 429 allocator.free(key); 430 allocator.free(val); 431 }; 432 } 433 434 return map; 435} 436 437fn parsePeerMeta( 438 allocator: std.mem.Allocator, 439 maybe_obj: ?std.json.Value, 440) std.StringHashMap(bool) { 441 var map = std.StringHashMap(bool).init(allocator); 442 443 const meta_obj = maybe_obj orelse return map; 444 if (meta_obj != .object) return map; 445 446 for (meta_obj.object.keys(), meta_obj.object.values()) |dep_name, meta_val| { 447 if (meta_val != .object) continue; 448 449 const is_optional = if (meta_val.object.get("optional")) |opt| (opt == .bool and opt.bool) else false; 450 451 if (is_optional) { 452 const key = allocator.dupe(u8, dep_name) catch continue; 453 map.put(key, true) catch allocator.free(key); 454 } 455 } 456 return map; 457} 458 459pub const PackageMetadata = struct { 460 allocator: std.mem.Allocator, 461 name: []const u8, 462 versions: std.ArrayListUnmanaged(VersionInfo), 463 dist_tag_latest: ?Version = null, 464 465 pub fn init(allocator: std.mem.Allocator, name: []const u8) !PackageMetadata { 466 return .{ 467 .allocator = allocator, 468 .name = try allocator.dupe(u8, name), 469 .versions = .{}, 470 .dist_tag_latest = null, 471 }; 472 } 473 474 pub fn deinit(self: *PackageMetadata) void { 475 if (self.dist_tag_latest) |*dtl| { 476 if (dtl.prerelease) |pre| self.allocator.free(pre); 477 if (dtl.build) |bld| self.allocator.free(bld); 478 } 479 for (self.versions.items) |*v| { 480 v.deinit(); 481 } 482 self.versions.deinit(self.allocator); 483 self.allocator.free(self.name); 484 } 485 486 pub fn parseFromJson(allocator: std.mem.Allocator, json_data: []const u8) !PackageMetadata { 487 const parsed = std.json.parseFromSlice(std.json.Value, allocator, json_data, .{}) catch { 488 return error.ParseError; 489 }; 490 defer parsed.deinit(); 491 492 const root = parsed.value; 493 if (root != .object) return error.ParseError; 494 495 const name = if (root.object.get("name")) |n| switch (n) { 496 .string => |s| s, 497 else => return error.ParseError, 498 } else return error.ParseError; 499 500 var metadata = try PackageMetadata.init(allocator, name); 501 errdefer metadata.deinit(); 502 503 metadata.dist_tag_latest = blk: { 504 const dt = root.object.get("dist-tags") orelse break :blk null; 505 if (dt != .object) break :blk null; 506 const latest_val = dt.object.get("latest") orelse break :blk null; 507 if (latest_val != .string) break :blk null; 508 var pl = Version.parse(latest_val.string) catch break :blk null; 509 if (pl.prerelease) |pre| pl.prerelease = allocator.dupe(u8, pre) catch null; 510 if (pl.build) |bld| pl.build = allocator.dupe(u8, bld) catch null; 511 break :blk pl; 512 }; 513 514 const versions_obj = root.object.get("versions") orelse return metadata; 515 if (versions_obj != .object) return metadata; 516 517 for (versions_obj.object.keys(), versions_obj.object.values()) |version_str, version_data| { 518 if (version_data != .object) continue; 519 520 var version = Version.parse(version_str) catch continue; 521 if (version.prerelease) |pre| { 522 version.prerelease = allocator.dupe(u8, pre) catch null; 523 } 524 if (version.build) |bld| { 525 version.build = allocator.dupe(u8, bld) catch null; 526 } 527 const dist = version_data.object.get("dist") orelse continue; 528 if (dist != .object) continue; 529 530 const tarball = if (dist.object.get("tarball")) |t| switch (t) { 531 .string => |s| s, 532 else => continue, 533 } else continue; 534 535 var integrity: [64]u8 = std.mem.zeroes([64]u8); 536 if (dist.object.get("integrity")) |i| { 537 if (i == .string) { 538 const int_str = i.string; 539 if (std.mem.startsWith(u8, int_str, "sha512-")) { 540 const b64 = int_str[7..]; 541 _ = std.base64.standard.Decoder.decode(&integrity, b64) catch {}; 542 } 543 } 544 } else if (dist.object.get("shasum")) |s| { 545 if (s == .string) { 546 const hex = s.string; 547 if (hex.len >= 40) { 548 for (0..20) |i| integrity[i] = std.fmt.parseInt(u8, hex[i * 2 ..][0..2], 16) catch 0; 549 } 550 } 551 } 552 553 const deps = parseDepsMap(allocator, version_data.object.get("dependencies")); 554 const opt_deps = parseDepsMap(allocator, version_data.object.get("optionalDependencies")); 555 const peer_deps = parseDepsMap(allocator, version_data.object.get("peerDependencies")); 556 const peer_meta = parsePeerMeta(allocator, version_data.object.get("peerDependenciesMeta")); 557 558 var os_filter: ?[]const u8 = null; 559 var cpu_filter: ?[]const u8 = null; 560 561 if (version_data.object.get("os")) |os_arr| { 562 if (os_arr == .array) { 563 var os_buf = std.ArrayListUnmanaged(u8){}; 564 for (os_arr.array.items, 0..) |item, i| { 565 if (item == .string) { 566 if (i > 0) os_buf.append(allocator, ',') catch {}; 567 os_buf.appendSlice(allocator, item.string) catch {}; 568 } 569 } 570 if (os_buf.items.len > 0) { 571 os_filter = os_buf.toOwnedSlice(allocator) catch null; 572 } else os_buf.deinit(allocator); 573 } 574 } 575 576 if (version_data.object.get("cpu")) |cpu_arr| { 577 if (cpu_arr == .array) { 578 var cpu_buf = std.ArrayListUnmanaged(u8){}; 579 for (cpu_arr.array.items, 0..) |item, i| { 580 if (item == .string) { 581 if (i > 0) cpu_buf.append(allocator, ',') catch {}; 582 cpu_buf.appendSlice(allocator, item.string) catch {}; 583 } 584 } 585 if (cpu_buf.items.len > 0) { 586 cpu_filter = cpu_buf.toOwnedSlice(allocator) catch null; 587 } else cpu_buf.deinit(allocator); 588 } 589 } 590 591 var libc_filter: ?[]const u8 = null; 592 if (version_data.object.get("libc")) |libc_arr| { 593 if (libc_arr == .array) { 594 var libc_buf = std.ArrayListUnmanaged(u8){}; 595 for (libc_arr.array.items, 0..) |item, i| { 596 if (item == .string) { 597 if (i > 0) libc_buf.append(allocator, ',') catch {}; 598 libc_buf.appendSlice(allocator, item.string) catch {}; 599 } 600 } 601 if (libc_buf.items.len > 0) { 602 libc_filter = libc_buf.toOwnedSlice(allocator) catch null; 603 } else libc_buf.deinit(allocator); 604 } 605 } 606 607 var bin = std.StringHashMap([]const u8).init(allocator); 608 if (version_data.object.get("bin")) |bin_val| { 609 if (bin_val == .object) { 610 for (bin_val.object.keys(), bin_val.object.values()) |key, val| { 611 if (val == .string) bin.put(allocator.dupe(u8, key) catch continue, allocator.dupe(u8, val.string) catch continue) catch {}; 612 } 613 } else if (bin_val == .string) { 614 const bin_name = allocator.dupe(u8, name) catch continue; 615 const bin_path = allocator.dupe(u8, bin_val.string) catch { 616 allocator.free(bin_name); 617 continue; 618 }; 619 bin.put(bin_name, bin_path) catch { 620 allocator.free(bin_name); 621 allocator.free(bin_path); 622 }; 623 } 624 } 625 626 try metadata.versions.append(allocator, .{ 627 .version = version, 628 .version_str = try allocator.dupe(u8, version_str), 629 .integrity = integrity, 630 .tarball_url = try allocator.dupe(u8, tarball), 631 .dependencies = deps, 632 .optional_dependencies = opt_deps, 633 .peer_dependencies = peer_deps, 634 .peer_dependencies_meta = peer_meta, 635 .os = os_filter, 636 .cpu = cpu_filter, 637 .libc = libc_filter, 638 .bin = bin, 639 .allocator = allocator, 640 }); 641 } 642 643 return metadata; 644 } 645}; 646 647pub const ResolvedPackage = struct { 648 name: intern.InternedString, 649 version: Version, 650 integrity: [64]u8, 651 tarball_url: []const u8, 652 dependencies: std.ArrayListUnmanaged(Dep), 653 depth: u32, 654 direct: bool, 655 parent_path: ?[]const u8, 656 has_bin: bool, 657 allocator: std.mem.Allocator, 658 659 pub const DepFlags = struct { 660 peer: bool = false, 661 dev: bool = false, 662 optional: bool = false, 663 }; 664 665 pub const Dep = struct { 666 name: intern.InternedString, 667 constraint: []const u8, 668 flags: DepFlags = .{}, 669 }; 670 671 pub fn deinit(self: *ResolvedPackage) void { 672 self.allocator.free(self.tarball_url); 673 if (self.parent_path) |p| self.allocator.free(p); 674 for (self.dependencies.items) |dep| { 675 self.allocator.free(dep.constraint); 676 } 677 self.dependencies.deinit(self.allocator); 678 } 679 680 pub fn installPath(self: *const ResolvedPackage, allocator: std.mem.Allocator) ![]const u8 { 681 if (self.parent_path) |parent| { 682 return std.fmt.allocPrint(allocator, "{s}/node_modules/{s}", .{ parent, self.name.slice() }); 683 } 684 return allocator.dupe(u8, self.name.slice()); 685 } 686}; 687 688pub const OnPackageResolvedFn = *const fn (pkg: *const ResolvedPackage, user_data: ?*anyopaque) void; 689 690pub const Resolver = struct { 691 allocator: std.mem.Allocator, 692 cache_allocator: std.mem.Allocator, 693 string_pool: *intern.StringPool, 694 http: *fetcher.Fetcher, 695 cache_db: ?*cache.CacheDB, 696 resolved: std.StringHashMap(*ResolvedPackage), 697 constraints: std.StringHashMap(std.ArrayListUnmanaged(Constraint)), 698 in_progress: std.StringHashMap(void), 699 registry_url: []const u8, 700 metadata_cache: *std.StringHashMap(PackageMetadata), 701 on_package_resolved: ?OnPackageResolvedFn, 702 on_package_resolved_data: ?*anyopaque, 703 704 pub fn init( 705 allocator: std.mem.Allocator, 706 cache_allocator: std.mem.Allocator, 707 string_pool: *intern.StringPool, 708 http: *fetcher.Fetcher, 709 cache_db: ?*cache.CacheDB, 710 registry_url: []const u8, 711 metadata_cache: *std.StringHashMap(PackageMetadata), 712 ) Resolver { 713 return .{ 714 .allocator = allocator, 715 .cache_allocator = cache_allocator, 716 .string_pool = string_pool, 717 .http = http, 718 .cache_db = cache_db, 719 .resolved = std.StringHashMap(*ResolvedPackage).init(allocator), 720 .constraints = std.StringHashMap(std.ArrayListUnmanaged(Constraint)).init(allocator), 721 .in_progress = std.StringHashMap(void).init(allocator), 722 .registry_url = registry_url, 723 .metadata_cache = metadata_cache, 724 .on_package_resolved = null, 725 .on_package_resolved_data = null, 726 }; 727 } 728 729 pub fn setOnPackageResolved(self: *Resolver, callback: OnPackageResolvedFn, user_data: ?*anyopaque) void { 730 self.on_package_resolved = callback; 731 self.on_package_resolved_data = user_data; 732 } 733 734 pub fn deinit(self: *Resolver) void { 735 var key_iter = self.resolved.keyIterator(); 736 while (key_iter.next()) |key| { 737 self.allocator.free(key.*); 738 } 739 740 var iter = self.resolved.valueIterator(); 741 while (iter.next()) |pkg| { 742 pkg.*.deinit(); 743 self.allocator.destroy(pkg.*); 744 } 745 self.resolved.deinit(); 746 747 var cons_key_iter = self.constraints.keyIterator(); 748 while (cons_key_iter.next()) |key| { 749 self.allocator.free(key.*); 750 } 751 752 var cons_iter = self.constraints.valueIterator(); 753 while (cons_iter.next()) |list| { 754 list.deinit(self.allocator); 755 } 756 self.constraints.deinit(); 757 self.in_progress.deinit(); 758 } 759 760 pub fn resolveFromPackageJson(self: *Resolver, path: []const u8) !void { 761 const path_z = try self.allocator.dupeZ(u8, path); 762 defer self.allocator.free(path_z); 763 764 var pkg_json = try json.PackageJson.parse(self.allocator, path_z); 765 defer pkg_json.deinit(self.allocator); 766 767 debug.log("pass 1: collecting constraints", .{}); 768 var pass1_start: u64 = @intCast(std.time.nanoTimestamp()); 769 self.http.initiateTarballConnectionsAsync(); 770 771 const ConstraintInfo = struct { 772 package_name: []const u8, 773 constraint: Constraint, 774 constraint_str: []const u8, 775 requester: []const u8, 776 depth: u32, 777 }; 778 779 var all_constraints = std.StringHashMap(std.ArrayListUnmanaged(ConstraintInfo)).init(self.allocator); 780 defer { 781 var iter = all_constraints.iterator(); 782 while (iter.next()) |entry| { 783 for (entry.value_ptr.items) |info| { 784 self.allocator.free(info.package_name); 785 self.allocator.free(info.constraint_str); 786 self.allocator.free(info.requester); 787 } 788 entry.value_ptr.deinit(self.allocator); 789 self.allocator.free(entry.key_ptr.*); 790 } 791 all_constraints.deinit(); 792 } 793 794 const CollectItem = struct { 795 name: []const u8, 796 constraint_str: []const u8, 797 requester: []const u8, 798 depth: u32, 799 }; 800 801 var collect_queue = std.ArrayListUnmanaged(CollectItem){}; 802 defer collect_queue.deinit(self.allocator); 803 804 var seen_collect = std.StringHashMap(void).init(self.allocator); 805 defer { 806 var key_iter = seen_collect.keyIterator(); 807 while (key_iter.next()) |k| self.allocator.free(k.*); 808 seen_collect.deinit(); 809 } 810 811 var dep_iter = pkg_json.dependencies.iterator(); 812 while (dep_iter.next()) |entry| { 813 try collect_queue.append(self.allocator, .{ 814 .name = entry.key_ptr.*, 815 .constraint_str = entry.value_ptr.*, 816 .requester = "root", 817 .depth = 0, 818 }); 819 } 820 821 var dev_iter = pkg_json.dev_dependencies.iterator(); 822 while (dev_iter.next()) |entry| { 823 try collect_queue.append(self.allocator, .{ 824 .name = entry.key_ptr.*, 825 .constraint_str = entry.value_ptr.*, 826 .requester = "root", 827 .depth = 0, 828 }); 829 } 830 831 var collect_level: u32 = 0; 832 while (collect_queue.items.len > 0) { 833 debug.log(" pass1 level {d}: {d} packages", .{ collect_level, collect_queue.items.len }); 834 835 var to_fetch = std.ArrayListUnmanaged([]const u8){}; 836 defer to_fetch.deinit(self.allocator); 837 838 for (collect_queue.items) |item| { 839 const spec = dependencySpec(item.name, item.constraint_str); 840 if (!self.metadata_cache.contains(spec.package_name)) { 841 var loaded_from_disk = false; 842 if (self.cache_db) |db| { 843 if (db.lookupMetadata(spec.package_name, self.allocator)) |json_data| { 844 const metadata = PackageMetadata.parseFromJson(self.cache_allocator, json_data) catch { 845 self.allocator.free(json_data); 846 continue; 847 }; 848 self.allocator.free(json_data); 849 const cache_key = self.cache_allocator.dupe(u8, spec.package_name) catch continue; 850 self.metadata_cache.put(cache_key, metadata) catch { 851 self.cache_allocator.free(cache_key); 852 continue; 853 }; 854 loaded_from_disk = true; 855 } 856 } 857 if (!loaded_from_disk) { 858 var already_listed = false; 859 for (to_fetch.items) |f| { 860 if (std.mem.eql(u8, f, spec.package_name)) { 861 already_listed = true; 862 break; 863 } 864 } 865 if (!already_listed) try to_fetch.append(self.allocator, spec.package_name); 866 } 867 } 868 } 869 870 const StreamContext = struct { 871 resolver: *Resolver, 872 prefetch_queue: *std.ArrayListUnmanaged([]const u8), 873 collect_queue_items: []const CollectItem, 874 allocator: std.mem.Allocator, 875 876 fn onMetadata(name: []const u8, data: ?[]const u8, has_error: bool, userdata: ?*anyopaque) void { 877 const ctx: *@This() = @ptrCast(@alignCast(userdata)); 878 if (has_error or data == null) return; 879 880 if (ctx.resolver.cache_db) |db| { 881 db.insertMetadata(name, data.?) catch {}; 882 } 883 884 const metadata = PackageMetadata.parseFromJson(ctx.resolver.cache_allocator, data.?) catch return; 885 const cache_key = ctx.resolver.cache_allocator.dupe(u8, name) catch return; 886 ctx.resolver.metadata_cache.put(cache_key, metadata) catch { 887 ctx.resolver.cache_allocator.free(cache_key); 888 return; 889 }; 890 891 for (ctx.collect_queue_items) |item| { 892 const spec = dependencySpec(item.name, item.constraint_str); 893 if (!std.mem.eql(u8, spec.package_name, name)) continue; 894 895 const constraint = Constraint.parse(spec.constraint) catch continue; 896 const best = ctx.resolver.selectBestVersion(&metadata, constraint) orelse continue; 897 if (!best.matchesPlatform()) continue; 898 899 var dep_it = best.dependencies.iterator(); 900 while (dep_it.next()) |entry| { 901 const dep_spec = dependencySpec(entry.key_ptr.*, entry.value_ptr.*); 902 if (ctx.resolver.metadata_cache.contains(dep_spec.package_name)) continue; 903 904 var already_queued = false; 905 for (ctx.prefetch_queue.items) |q| { 906 if (std.mem.eql(u8, q, dep_spec.package_name)) { 907 already_queued = true; 908 break; 909 } 910 } 911 if (!already_queued) ctx.prefetch_queue.append(ctx.allocator, dep_spec.package_name) catch {}; 912 } 913 break; 914 } 915 } 916 }; 917 918 var next_collect = std.ArrayListUnmanaged(CollectItem){}; 919 errdefer next_collect.deinit(self.allocator); 920 921 var prefetch_queue = std.ArrayListUnmanaged([]const u8){}; 922 defer prefetch_queue.deinit(self.allocator); 923 924 if (to_fetch.items.len > 0) { 925 var stream_ctx = StreamContext{ 926 .resolver = self, 927 .prefetch_queue = &prefetch_queue, 928 .collect_queue_items = collect_queue.items, 929 .allocator = self.allocator, 930 }; 931 932 try self.http.fetchMetadataStreaming( 933 to_fetch.items, 934 self.allocator, 935 StreamContext.onMetadata, 936 &stream_ctx, 937 ); 938 939 if (prefetch_queue.items.len > 0) { 940 debug.log(" prefetch: queued {d} next-level packages", .{prefetch_queue.items.len}); 941 self.http.fetchMetadataStreaming( 942 prefetch_queue.items, 943 self.allocator, 944 StreamContext.onMetadata, 945 &stream_ctx, 946 ) catch {}; 947 } 948 } 949 950 for (collect_queue.items) |item| { 951 const seen_key = std.fmt.allocPrint(self.allocator, "{s}@{s}@{s}", .{ item.name, item.constraint_str, item.requester }) catch continue; 952 if (seen_collect.contains(seen_key)) { 953 self.allocator.free(seen_key); 954 continue; 955 } 956 try seen_collect.put(seen_key, {}); 957 958 const spec = dependencySpec(item.name, item.constraint_str); 959 const constraint = Constraint.parse(spec.constraint) catch continue; 960 const gop = try all_constraints.getOrPut(spec.install_name); 961 if (!gop.found_existing) { 962 gop.key_ptr.* = try self.allocator.dupe(u8, spec.install_name); 963 gop.value_ptr.* = .{}; 964 } 965 try gop.value_ptr.append(self.allocator, .{ 966 .package_name = try self.allocator.dupe(u8, spec.package_name), 967 .constraint = constraint, 968 .constraint_str = try self.allocator.dupe(u8, spec.constraint), 969 .requester = try self.allocator.dupe(u8, item.requester), 970 .depth = item.depth, 971 }); 972 973 if (self.metadata_cache.get(spec.package_name)) |metadata| { 974 const best = self.selectBestVersion(&metadata, constraint) orelse continue; 975 if (!best.matchesPlatform()) continue; 976 977 var dep_it = best.dependencies.iterator(); 978 while (dep_it.next()) |entry| { 979 try next_collect.append(self.allocator, .{ 980 .name = entry.key_ptr.*, 981 .constraint_str = entry.value_ptr.*, 982 .requester = item.name, 983 .depth = item.depth + 1, 984 }); 985 } 986 987 var opt_it = best.optional_dependencies.iterator(); 988 while (opt_it.next()) |entry| { 989 const opt_spec = dependencySpec(entry.key_ptr.*, entry.value_ptr.*); 990 if (self.metadata_cache.get(opt_spec.package_name)) |opt_meta| { 991 const opt_con = Constraint.parse(opt_spec.constraint) catch continue; 992 const opt_best = self.selectBestVersion(&opt_meta, opt_con) orelse continue; 993 if (!opt_best.matchesPlatform()) continue; 994 } 995 try next_collect.append(self.allocator, .{ 996 .name = entry.key_ptr.*, 997 .constraint_str = entry.value_ptr.*, 998 .requester = item.name, 999 .depth = item.depth + 1, 1000 }); 1001 } 1002 1003 var peer_it = best.peer_dependencies.iterator(); 1004 while (peer_it.next()) |entry| { 1005 if (best.peer_dependencies_meta.contains(entry.key_ptr.*)) continue; 1006 try next_collect.append(self.allocator, .{ 1007 .name = entry.key_ptr.*, 1008 .constraint_str = entry.value_ptr.*, 1009 .requester = item.name, 1010 .depth = item.depth + 1, 1011 }); 1012 } 1013 } 1014 } 1015 1016 collect_queue.deinit(self.allocator); 1017 collect_queue = next_collect; 1018 collect_level += 1; 1019 } 1020 1021 pass1_start = debug.timer("pass 1 complete", pass1_start); 1022 debug.log(" collected constraints for {d} packages", .{all_constraints.count()}); 1023 debug.log("computing optimal versions", .{}); 1024 1025 var optimal_versions = std.StringHashMap(*const VersionInfo).init(self.allocator); 1026 defer optimal_versions.deinit(); 1027 1028 var pkg_iter = all_constraints.iterator(); 1029 while (pkg_iter.next()) |entry| { 1030 const install_name = entry.key_ptr.*; 1031 const constraint_list = entry.value_ptr.items; 1032 if (constraint_list.len == 0) continue; 1033 1034 const package_name = constraint_list[0].package_name; 1035 if (self.metadata_cache.get(package_name)) |metadata| { 1036 var plain_constraints = try self.allocator.alloc(Constraint, constraint_list.len); 1037 defer self.allocator.free(plain_constraints); 1038 for (constraint_list, 0..) |info, i| { 1039 plain_constraints[i] = info.constraint; 1040 } 1041 1042 if (self.selectBestVersionForConstraints(&metadata, plain_constraints)) |best| { 1043 try optimal_versions.put(install_name, best); 1044 } else { 1045 const best = self.selectVersionSatisfyingMost(&metadata, constraint_list); 1046 if (best) |b| { 1047 if (b.version.prerelease) |pre| { 1048 debug.log(" {s}: optimal={d}.{d}.{d}-{s} (satisfies {d}/{d} constraints)", .{ 1049 install_name, b.version.major, b.version.minor, b.version.patch, pre, 1050 self.countSatisfied(&metadata, b, constraint_list), constraint_list.len, 1051 }); 1052 } else { 1053 debug.log(" {s}: optimal={d}.{d}.{d} (satisfies {d}/{d} constraints)", .{ 1054 install_name, b.version.major, b.version.minor, b.version.patch, 1055 self.countSatisfied(&metadata, b, constraint_list), constraint_list.len, 1056 }); 1057 } 1058 try optimal_versions.put(install_name, b); 1059 } 1060 } 1061 } 1062 } 1063 1064 pass1_start = debug.timer("optimal versions computed", pass1_start); 1065 debug.log("pass 2: resolving with optimal versions", .{}); 1066 1067 const WorkItem = struct { 1068 name: []const u8, 1069 constraint: []const u8, 1070 depth: u32, 1071 direct: bool, 1072 parent_name: ?[]const u8, 1073 }; 1074 1075 var queue = std.ArrayListUnmanaged(WorkItem){}; 1076 defer { 1077 for (queue.items) |item| if (item.parent_name) |p| self.allocator.free(p); 1078 queue.deinit(self.allocator); 1079 } 1080 1081 dep_iter = pkg_json.dependencies.iterator(); 1082 while (dep_iter.next()) |entry| { 1083 try queue.append(self.allocator, .{ 1084 .name = entry.key_ptr.*, 1085 .constraint = entry.value_ptr.*, 1086 .depth = 0, 1087 .direct = true, 1088 .parent_name = null, 1089 }); 1090 } 1091 dev_iter = pkg_json.dev_dependencies.iterator(); 1092 while (dev_iter.next()) |entry| { 1093 try queue.append(self.allocator, .{ 1094 .name = entry.key_ptr.*, 1095 .constraint = entry.value_ptr.*, 1096 .depth = 0, 1097 .direct = true, 1098 .parent_name = null, 1099 }); 1100 } 1101 1102 var processed = std.StringHashMap(void).init(self.allocator); 1103 defer { 1104 var key_iter = processed.keyIterator(); 1105 while (key_iter.next()) |k| self.allocator.free(k.*); 1106 processed.deinit(); 1107 } 1108 1109 var level: u32 = 0; 1110 while (queue.items.len > 0) { 1111 const level_start: u64 = @intCast(std.time.nanoTimestamp()); 1112 debug.log(" pass2 level {d}: {d} packages", .{ level, queue.items.len }); 1113 1114 var next_queue = std.ArrayListUnmanaged(WorkItem){}; 1115 errdefer next_queue.deinit(self.allocator); 1116 1117 for (queue.items) |item| { 1118 const key = std.fmt.allocPrint(self.allocator, "{s}|{s}@{s}", .{ 1119 item.parent_name orelse "", 1120 item.name, 1121 item.constraint, 1122 }) catch continue; 1123 if (processed.contains(key)) { 1124 self.allocator.free(key); 1125 continue; 1126 } 1127 try processed.put(key, {}); 1128 1129 const pkg = self.resolveSingleWithOptimal(item.name, item.constraint, item.depth, item.direct, item.parent_name, &optimal_versions) catch |err| { 1130 debug.log(" failed to resolve {s}: {}", .{ item.name, err }); 1131 continue; 1132 }; 1133 1134 const pkg_install_path = pkg.installPath(self.allocator) catch continue; 1135 defer self.allocator.free(pkg_install_path); 1136 1137 for (pkg.dependencies.items) |dep| { 1138 const dep_key = std.fmt.allocPrint(self.allocator, "{s}|{s}@{s}", .{ 1139 pkg_install_path, 1140 dep.name.slice(), 1141 dep.constraint, 1142 }) catch continue; 1143 defer self.allocator.free(dep_key); 1144 if (!processed.contains(dep_key)) { 1145 try next_queue.append(self.allocator, .{ 1146 .name = dep.name.slice(), 1147 .constraint = dep.constraint, 1148 .depth = item.depth + 1, 1149 .direct = false, 1150 .parent_name = try self.allocator.dupe(u8, pkg_install_path), 1151 }); 1152 } 1153 } 1154 } 1155 _ = debug.timer(" resolve + queue next", level_start); 1156 1157 const completed = self.http.tick(); 1158 if (completed > 0) { 1159 debug.log(" tarballs: {d} completed, {d} in flight", .{ completed, self.http.pendingTarballCount() }); 1160 } 1161 1162 for (queue.items) |item| if (item.parent_name) |p| self.allocator.free(p); 1163 queue.deinit(self.allocator); 1164 queue = next_queue; 1165 level += 1; 1166 } 1167 } 1168 1169 fn countSatisfied(_: *Resolver, _: *const PackageMetadata, version_info: *const VersionInfo, constraint_list: anytype) usize { 1170 var count: usize = 0; 1171 for (constraint_list) |info| { 1172 if (info.constraint.satisfies(version_info.version)) count += 1; 1173 } 1174 return count; 1175 } 1176 1177 fn selectVersionSatisfyingMost(_: *Resolver, metadata: *const PackageMetadata, constraint_list: anytype) ?*const VersionInfo { 1178 var best: ?*const VersionInfo = null; 1179 var best_score: i64 = -1; 1180 1181 var want_prerelease = false; 1182 for (constraint_list) |info| { 1183 if (info.constraint.version.prerelease != null) { 1184 want_prerelease = true; 1185 break; 1186 } 1187 } 1188 1189 for (metadata.versions.items) |*v| { 1190 if (v.version.prerelease != null and !want_prerelease) continue; 1191 if (!v.matchesPlatform()) continue; 1192 1193 var score: i64 = 0; 1194 for (constraint_list) |info| { 1195 if (info.constraint.satisfies(v.version)) { 1196 const weight: i64 = @intCast(1000 / (info.depth + 1)); 1197 score += weight; 1198 } 1199 } 1200 1201 if (score > best_score or (score == best_score and best != null and v.version.order(best.?.version) == .gt)) { 1202 best = v; 1203 best_score = score; 1204 } 1205 } 1206 1207 return best; 1208 } 1209 1210 fn resolveSingleWithOptimal( 1211 self: *Resolver, 1212 name: []const u8, 1213 constraint_str: []const u8, 1214 depth: u32, 1215 direct: bool, 1216 parent_name: ?[]const u8, 1217 optimal_versions: *std.StringHashMap(*const VersionInfo), 1218 ) !*ResolvedPackage { 1219 const dep_spec = dependencySpec(name, constraint_str); 1220 const constraint = try Constraint.parse(dep_spec.constraint); 1221 1222 if (self.resolved.get(dep_spec.install_name)) |existing_pkg| { 1223 if (constraint.satisfies(existing_pkg.version)) { 1224 if (direct) existing_pkg.direct = true; 1225 if (depth < existing_pkg.depth) existing_pkg.depth = depth; 1226 return existing_pkg; 1227 } 1228 1229 if (parent_name) |parent| { 1230 var metadata = try self.fetchMetadata(dep_spec.package_name); 1231 const nested_best = self.selectBestVersion(&metadata, constraint) orelse return existing_pkg; 1232 if (!nested_best.matchesPlatform()) return existing_pkg; 1233 1234 debug.log(" nested: {s}@{d}.{d}.{d} under {s} (hoisted: {d}.{d}.{d})", .{ 1235 dep_spec.install_name, 1236 nested_best.version.major, 1237 nested_best.version.minor, 1238 nested_best.version.patch, 1239 parent, 1240 existing_pkg.version.major, 1241 existing_pkg.version.minor, 1242 existing_pkg.version.patch, 1243 }); 1244 1245 return try self.createNestedPackage(dep_spec.install_name, nested_best, depth, parent); 1246 } 1247 1248 return existing_pkg; 1249 } 1250 1251 var metadata = try self.fetchMetadata(dep_spec.package_name); 1252 const version_info = blk: { 1253 if (optimal_versions.get(dep_spec.install_name)) |optimal| { 1254 if (constraint.satisfies(optimal.version) and optimal.matchesPlatform()) break :blk optimal; 1255 } 1256 break :blk self.selectBestVersion(&metadata, constraint) orelse return error.NoMatchingVersion; 1257 }; 1258 1259 if (!version_info.matchesPlatform()) return error.PlatformMismatch; 1260 1261 const cons_gop = try self.constraints.getOrPut(dep_spec.install_name); 1262 if (!cons_gop.found_existing) { 1263 cons_gop.key_ptr.* = try self.allocator.dupe(u8, dep_spec.install_name); 1264 cons_gop.value_ptr.* = .{}; 1265 } 1266 try cons_gop.value_ptr.append(self.allocator, constraint); 1267 1268 const pkg = try self.allocator.create(ResolvedPackage); 1269 errdefer self.allocator.destroy(pkg); 1270 1271 pkg.* = .{ 1272 .name = try self.string_pool.intern(dep_spec.install_name), 1273 .version = version_info.version, 1274 .integrity = version_info.integrity, 1275 .tarball_url = try self.allocator.dupe(u8, version_info.tarball_url), 1276 .dependencies = .{}, 1277 .depth = depth, 1278 .direct = direct, 1279 .parent_path = null, 1280 .has_bin = version_info.bin.count() > 0, 1281 .allocator = self.allocator, 1282 }; 1283 1284 const name_key = try self.allocator.dupe(u8, dep_spec.install_name); 1285 errdefer self.allocator.free(name_key); 1286 try self.resolved.put(name_key, pkg); 1287 1288 var dep_it = version_info.dependencies.iterator(); 1289 while (dep_it.next()) |entry| { 1290 try pkg.dependencies.append(self.allocator, .{ 1291 .name = try self.string_pool.intern(entry.key_ptr.*), 1292 .constraint = try self.allocator.dupe(u8, entry.value_ptr.*), 1293 .flags = .{}, 1294 }); 1295 } 1296 1297 var opt_it = version_info.optional_dependencies.iterator(); 1298 while (opt_it.next()) |entry| { 1299 const opt_spec = dependencySpec(entry.key_ptr.*, entry.value_ptr.*); 1300 if (self.metadata_cache.get(opt_spec.package_name)) |opt_meta| { 1301 const opt_con = Constraint.parse(opt_spec.constraint) catch continue; 1302 const opt_best = self.selectBestVersion(&opt_meta, opt_con) orelse continue; 1303 if (!opt_best.matchesPlatform()) continue; 1304 } 1305 try pkg.dependencies.append(self.allocator, .{ 1306 .name = try self.string_pool.intern(entry.key_ptr.*), 1307 .constraint = try self.allocator.dupe(u8, entry.value_ptr.*), 1308 .flags = .{ .optional = true }, 1309 }); 1310 } 1311 1312 var peer_it = version_info.peer_dependencies.iterator(); 1313 while (peer_it.next()) |entry| { 1314 if (version_info.peer_dependencies_meta.contains(entry.key_ptr.*)) continue; 1315 try pkg.dependencies.append(self.allocator, .{ 1316 .name = try self.string_pool.intern(entry.key_ptr.*), 1317 .constraint = try self.allocator.dupe(u8, entry.value_ptr.*), 1318 .flags = .{ .peer = true }, 1319 }); 1320 } 1321 1322 if (self.on_package_resolved) |callback| { 1323 callback(pkg, self.on_package_resolved_data); 1324 } 1325 1326 return pkg; 1327 } 1328 1329 fn resolveSingle(self: *Resolver, name: []const u8, constraint_str: []const u8, depth: u32, direct: bool, parent_name: ?[]const u8) !*ResolvedPackage { 1330 const dep_spec = dependencySpec(name, constraint_str); 1331 const constraint = try Constraint.parse(dep_spec.constraint); 1332 1333 if (self.resolved.get(dep_spec.install_name)) |existing_pkg| { 1334 if (constraint.satisfies(existing_pkg.version)) { 1335 if (direct) existing_pkg.direct = true; 1336 if (depth < existing_pkg.depth) existing_pkg.depth = depth; 1337 return existing_pkg; 1338 } 1339 1340 const cons_gop = try self.constraints.getOrPut(dep_spec.install_name); 1341 if (!cons_gop.found_existing) { 1342 cons_gop.key_ptr.* = try self.allocator.dupe(u8, dep_spec.install_name); 1343 cons_gop.value_ptr.* = .{}; 1344 } 1345 try cons_gop.value_ptr.append(self.allocator, constraint); 1346 1347 var metadata = try self.fetchMetadata(dep_spec.package_name); 1348 const all_constraints = cons_gop.value_ptr.items; 1349 const best = self.selectBestVersionForConstraints(&metadata, all_constraints); 1350 1351 if (best) |b| { 1352 if (!b.matchesPlatform()) { 1353 return error.PlatformMismatch; 1354 } 1355 1356 if (b.version.order(existing_pkg.version) != .eq) { 1357 debug.log(" re-resolve {s}: {d}.{d}.{d} -> {d}.{d}.{d}", .{ 1358 dep_spec.install_name, 1359 existing_pkg.version.major, 1360 existing_pkg.version.minor, 1361 existing_pkg.version.patch, 1362 b.version.major, 1363 b.version.minor, 1364 b.version.patch, 1365 }); 1366 1367 existing_pkg.version = b.version; 1368 existing_pkg.integrity = b.integrity; 1369 self.allocator.free(existing_pkg.tarball_url); 1370 existing_pkg.tarball_url = try self.allocator.dupe(u8, b.tarball_url); 1371 existing_pkg.has_bin = b.bin.count() > 0; 1372 1373 if (self.on_package_resolved) |callback| { 1374 callback(existing_pkg, self.on_package_resolved_data); 1375 } 1376 } 1377 1378 if (direct) existing_pkg.direct = true; 1379 if (depth < existing_pkg.depth) existing_pkg.depth = depth; 1380 return existing_pkg; 1381 } else { 1382 if (parent_name) |parent| { 1383 const nested_best = self.selectBestVersion(&metadata, constraint) orelse { 1384 debug.log(" nested: no version for {s} {s}", .{ dep_spec.install_name, dep_spec.constraint }); 1385 return existing_pkg; 1386 }; 1387 1388 if (!nested_best.matchesPlatform()) return existing_pkg; 1389 debug.log(" nested: {s}@{d}.{d}.{d} under {s} (hoisted: {d}.{d}.{d})", .{ 1390 dep_spec.install_name, 1391 nested_best.version.major, 1392 nested_best.version.minor, 1393 nested_best.version.patch, 1394 parent, 1395 existing_pkg.version.major, 1396 existing_pkg.version.minor, 1397 existing_pkg.version.patch, 1398 }); 1399 1400 return try self.createNestedPackage(dep_spec.install_name, nested_best, depth, parent); 1401 } else { 1402 debug.log(" version conflict for {s}: no version satisfies all constraints", .{dep_spec.install_name}); 1403 return existing_pkg; 1404 } 1405 } 1406 } 1407 1408 const cons_gop = try self.constraints.getOrPut(dep_spec.install_name); 1409 if (!cons_gop.found_existing) { 1410 cons_gop.key_ptr.* = try self.allocator.dupe(u8, dep_spec.install_name); 1411 cons_gop.value_ptr.* = .{}; 1412 } 1413 try cons_gop.value_ptr.append(self.allocator, constraint); 1414 1415 var metadata = try self.fetchMetadata(dep_spec.package_name); 1416 const best = self.selectBestVersion(&metadata, constraint) orelse { 1417 return error.NoMatchingVersion; 1418 }; 1419 1420 if (!best.matchesPlatform()) { 1421 return error.PlatformMismatch; 1422 } 1423 1424 const pkg = try self.allocator.create(ResolvedPackage); 1425 errdefer self.allocator.destroy(pkg); 1426 1427 pkg.* = .{ 1428 .name = try self.string_pool.intern(dep_spec.install_name), 1429 .version = best.version, 1430 .integrity = best.integrity, 1431 .tarball_url = try self.allocator.dupe(u8, best.tarball_url), 1432 .dependencies = .{}, 1433 .depth = depth, 1434 .direct = direct, 1435 .parent_path = null, 1436 .has_bin = best.bin.count() > 0, 1437 .allocator = self.allocator, 1438 }; 1439 1440 const name_key = try self.allocator.dupe(u8, dep_spec.install_name); 1441 errdefer self.allocator.free(name_key); 1442 try self.resolved.put(name_key, pkg); 1443 1444 var dep_iter = best.dependencies.iterator(); 1445 while (dep_iter.next()) |entry| { 1446 const dep_name = entry.key_ptr.*; 1447 const dep_constraint = entry.value_ptr.*; 1448 1449 try pkg.dependencies.append(self.allocator, .{ 1450 .name = try self.string_pool.intern(dep_name), 1451 .constraint = try self.allocator.dupe(u8, dep_constraint), 1452 .flags = .{}, 1453 }); 1454 } 1455 1456 var opt_iter = best.optional_dependencies.iterator(); 1457 while (opt_iter.next()) |entry| { 1458 const dep_name = entry.key_ptr.*; 1459 const dep_constraint = entry.value_ptr.*; 1460 const opt_spec = dependencySpec(dep_name, dep_constraint); 1461 1462 if (self.metadata_cache.get(opt_spec.package_name)) |opt_metadata| { 1463 const opt_constraint = Constraint.parse(opt_spec.constraint) catch continue; 1464 const opt_best = self.selectBestVersion(&opt_metadata, opt_constraint) orelse continue; 1465 1466 if (!opt_best.matchesPlatform()) continue; 1467 } 1468 1469 try pkg.dependencies.append(self.allocator, .{ 1470 .name = try self.string_pool.intern(dep_name), 1471 .constraint = try self.allocator.dupe(u8, dep_constraint), 1472 .flags = .{ .optional = true }, 1473 }); 1474 } 1475 1476 var peer_iter = best.peer_dependencies.iterator(); 1477 while (peer_iter.next()) |entry| { 1478 const dep_name = entry.key_ptr.*; 1479 const dep_constraint = entry.value_ptr.*; 1480 1481 if (best.peer_dependencies_meta.contains(dep_name)) continue; 1482 try pkg.dependencies.append(self.allocator, .{ 1483 .name = try self.string_pool.intern(dep_name), 1484 .constraint = try self.allocator.dupe(u8, dep_constraint), 1485 .flags = .{ .peer = true }, 1486 }); 1487 } 1488 1489 if (self.on_package_resolved) |callback| { 1490 callback(pkg, self.on_package_resolved_data); 1491 } 1492 1493 return pkg; 1494 } 1495 1496 fn createNestedPackage(self: *Resolver, name: []const u8, version_info: *const VersionInfo, depth: u32, parent_path: []const u8) !*ResolvedPackage { 1497 const nested_key = try std.fmt.allocPrint(self.allocator, "{s}/node_modules/{s}", .{ parent_path, name }); 1498 errdefer self.allocator.free(nested_key); 1499 1500 if (self.resolved.get(nested_key)) |existing| { 1501 self.allocator.free(nested_key); 1502 return existing; 1503 } 1504 1505 const pkg = try self.allocator.create(ResolvedPackage); 1506 errdefer self.allocator.destroy(pkg); 1507 1508 pkg.* = .{ 1509 .name = try self.string_pool.intern(name), 1510 .version = version_info.version, 1511 .integrity = version_info.integrity, 1512 .tarball_url = try self.allocator.dupe(u8, version_info.tarball_url), 1513 .dependencies = .{}, 1514 .depth = depth, 1515 .direct = false, 1516 .parent_path = try self.allocator.dupe(u8, parent_path), 1517 .has_bin = version_info.bin.count() > 0, 1518 .allocator = self.allocator, 1519 }; 1520 1521 try self.resolved.put(nested_key, pkg); 1522 1523 var dep_iter = version_info.dependencies.iterator(); 1524 while (dep_iter.next()) |entry| { 1525 try pkg.dependencies.append(self.allocator, .{ 1526 .name = try self.string_pool.intern(entry.key_ptr.*), 1527 .constraint = try self.allocator.dupe(u8, entry.value_ptr.*), 1528 .flags = .{}, 1529 }); 1530 } 1531 1532 var opt_iter = version_info.optional_dependencies.iterator(); 1533 while (opt_iter.next()) |entry| { 1534 const dep_name = entry.key_ptr.*; 1535 const dep_constraint = entry.value_ptr.*; 1536 const opt_spec = dependencySpec(dep_name, dep_constraint); 1537 1538 if (self.metadata_cache.get(opt_spec.package_name)) |opt_metadata| { 1539 const opt_constraint = Constraint.parse(opt_spec.constraint) catch continue; 1540 const opt_best = self.selectBestVersion(&opt_metadata, opt_constraint) orelse continue; 1541 if (!opt_best.matchesPlatform()) continue; 1542 } 1543 1544 try pkg.dependencies.append(self.allocator, .{ 1545 .name = try self.string_pool.intern(dep_name), 1546 .constraint = try self.allocator.dupe(u8, dep_constraint), 1547 .flags = .{ .optional = true }, 1548 }); 1549 } 1550 1551 if (self.on_package_resolved) |callback| { 1552 callback(pkg, self.on_package_resolved_data); 1553 } 1554 1555 return pkg; 1556 } 1557 1558 pub fn resolve(self: *Resolver, name: []const u8, constraint_str: []const u8, depth: u32) !*ResolvedPackage { 1559 if (self.in_progress.contains(name)) { 1560 return error.CyclicDependency; 1561 } 1562 const in_progress_key = try self.allocator.dupe(u8, name); 1563 try self.in_progress.put(in_progress_key, {}); 1564 defer { 1565 _ = self.in_progress.remove(name); 1566 self.allocator.free(in_progress_key); 1567 } 1568 1569 const dep_spec = dependencySpec(name, constraint_str); 1570 const constraint = try Constraint.parse(dep_spec.constraint); 1571 const cons_gop = try self.constraints.getOrPut(dep_spec.install_name); 1572 1573 if (!cons_gop.found_existing) { 1574 cons_gop.key_ptr.* = try self.allocator.dupe(u8, dep_spec.install_name); 1575 cons_gop.value_ptr.* = .{}; 1576 } 1577 try cons_gop.value_ptr.append(self.allocator, constraint); 1578 1579 if (self.resolved.get(dep_spec.install_name)) |existing_pkg| { 1580 if (constraint.satisfies(existing_pkg.version)) { 1581 if (depth == 0) existing_pkg.direct = true; 1582 if (depth < existing_pkg.depth) existing_pkg.depth = depth; 1583 return existing_pkg; 1584 } 1585 1586 var metadata = try self.fetchMetadata(dep_spec.package_name); 1587 const all_constraints = cons_gop.value_ptr.items; 1588 const best = self.selectBestVersionForConstraints(&metadata, all_constraints) orelse { 1589 debug.log(" version conflict for {s}: no version satisfies all constraints", .{dep_spec.install_name}); 1590 return existing_pkg; 1591 }; 1592 1593 if (best.version.order(existing_pkg.version) != .eq) { 1594 debug.log(" re-resolve {s}: {d}.{d}.{d} -> {d}.{d}.{d}", .{ 1595 dep_spec.install_name, 1596 existing_pkg.version.major, 1597 existing_pkg.version.minor, 1598 existing_pkg.version.patch, 1599 best.version.major, 1600 best.version.minor, 1601 best.version.patch, 1602 }); 1603 1604 existing_pkg.version = best.version; 1605 existing_pkg.integrity = best.integrity; 1606 self.allocator.free(existing_pkg.tarball_url); 1607 existing_pkg.tarball_url = try self.allocator.dupe(u8, best.tarball_url); 1608 existing_pkg.has_bin = best.bin.count() > 0; 1609 } 1610 1611 if (depth == 0) existing_pkg.direct = true; 1612 if (depth < existing_pkg.depth) existing_pkg.depth = depth; 1613 return existing_pkg; 1614 } 1615 1616 var metadata = try self.fetchMetadata(dep_spec.package_name); 1617 const all_constraints = cons_gop.value_ptr.items; 1618 const best = self.selectBestVersionForConstraints(&metadata, all_constraints) orelse { 1619 return error.NoMatchingVersion; 1620 }; 1621 1622 const pkg = try self.allocator.create(ResolvedPackage); 1623 errdefer self.allocator.destroy(pkg); 1624 1625 pkg.* = .{ 1626 .name = try self.string_pool.intern(dep_spec.install_name), 1627 .version = best.version, 1628 .integrity = best.integrity, 1629 .tarball_url = try self.allocator.dupe(u8, best.tarball_url), 1630 .dependencies = .{}, 1631 .depth = depth, 1632 .direct = (depth == 0), 1633 .parent_path = null, 1634 .has_bin = best.bin.count() > 0, 1635 .allocator = self.allocator, 1636 }; 1637 1638 const name_key = try self.allocator.dupe(u8, dep_spec.install_name); 1639 errdefer self.allocator.free(name_key); 1640 try self.resolved.put(name_key, pkg); 1641 1642 var dep_iter = best.dependencies.iterator(); 1643 while (dep_iter.next()) |entry| { 1644 const dep_name = entry.key_ptr.*; 1645 const dep_constraint = entry.value_ptr.*; 1646 1647 _ = self.resolve(dep_name, dep_constraint, depth + 1) catch |err| { 1648 std.log.debug("Skipping dep {s}: {}", .{ dep_name, err }); 1649 continue; 1650 }; 1651 1652 try pkg.dependencies.append(self.allocator, .{ 1653 .name = try self.string_pool.intern(dep_name), 1654 .constraint = try self.allocator.dupe(u8, dep_constraint), 1655 .flags = .{}, 1656 }); 1657 } 1658 1659 var opt_iter = best.optional_dependencies.iterator(); 1660 while (opt_iter.next()) |entry| { 1661 const dep_name = entry.key_ptr.*; 1662 const dep_constraint = entry.value_ptr.*; 1663 1664 const resolved_opt = self.resolve(dep_name, dep_constraint, depth + 1) catch { 1665 continue; 1666 }; 1667 1668 const opt_spec = dependencySpec(dep_name, dep_constraint); 1669 const opt_metadata = self.fetchMetadata(opt_spec.package_name) catch continue; 1670 const opt_constraint = Constraint.parse(opt_spec.constraint) catch continue; 1671 const opt_best = self.selectBestVersion(&opt_metadata, opt_constraint) orelse continue; 1672 1673 if (!opt_best.matchesPlatform()) { 1674 if (self.resolved.fetchRemove(opt_spec.install_name)) |kv| { 1675 self.allocator.free(kv.key); 1676 kv.value.deinit(); 1677 self.allocator.destroy(kv.value); 1678 } 1679 continue; 1680 } 1681 1682 _ = resolved_opt; 1683 try pkg.dependencies.append(self.allocator, .{ 1684 .name = try self.string_pool.intern(dep_name), 1685 .constraint = try self.allocator.dupe(u8, dep_constraint), 1686 .flags = .{ .optional = true }, 1687 }); 1688 } 1689 1690 var peer_iter = best.peer_dependencies.iterator(); 1691 while (peer_iter.next()) |entry| { 1692 const dep_name = entry.key_ptr.*; 1693 const dep_constraint = entry.value_ptr.*; 1694 1695 if (best.peer_dependencies_meta.contains(dep_name)) continue; 1696 _ = self.resolve(dep_name, dep_constraint, depth + 1) catch continue; 1697 try pkg.dependencies.append(self.allocator, .{ 1698 .name = try self.string_pool.intern(dep_name), 1699 .constraint = try self.allocator.dupe(u8, dep_constraint), 1700 .flags = .{ .peer = true }, 1701 }); 1702 } 1703 1704 return pkg; 1705 } 1706 1707 fn fetchMetadata(self: *Resolver, name: []const u8) !PackageMetadata { 1708 if (self.metadata_cache.get(name)) |cached| { 1709 debug.log(" metadata: {s} (memory cache)", .{name}); 1710 return PackageMetadata{ 1711 .allocator = cached.allocator, 1712 .name = cached.name, 1713 .versions = cached.versions, 1714 }; 1715 } 1716 1717 if (self.cache_db) |db| { 1718 if (db.lookupMetadata(name, self.allocator)) |json_data| { 1719 defer self.allocator.free(json_data); 1720 const metadata = PackageMetadata.parseFromJson(self.cache_allocator, json_data) catch { 1721 return self.fetchFromNetwork(name); 1722 }; 1723 1724 debug.log(" metadata: {s} (disk cache)", .{name}); 1725 const cache_key = try self.cache_allocator.dupe(u8, name); 1726 try self.metadata_cache.put(cache_key, metadata); 1727 return self.metadata_cache.get(name).?; 1728 } 1729 } 1730 1731 return self.fetchFromNetwork(name); 1732 } 1733 1734 fn fetchFromNetwork(self: *Resolver, name: []const u8) !PackageMetadata { 1735 debug.log(" metadata: {s} (fetch)", .{name}); 1736 const json_data = try self.http.fetchMetadata(name, self.allocator); 1737 defer self.allocator.free(json_data); 1738 1739 if (self.cache_db) |db| { 1740 db.insertMetadata(name, json_data) catch {}; 1741 } 1742 1743 const metadata = try PackageMetadata.parseFromJson(self.cache_allocator, json_data); 1744 const cache_key = try self.cache_allocator.dupe(u8, name); 1745 1746 try self.metadata_cache.put(cache_key, metadata); 1747 return self.metadata_cache.get(name).?; 1748 } 1749 1750 fn selectBestVersion(_: *Resolver, metadata: *const PackageMetadata, constraint: Constraint) ?*const VersionInfo { 1751 if (constraint.kind == .any) for (metadata.versions.items) |*v| { 1752 const dtl = metadata.dist_tag_latest orelse break; 1753 if (v.version.order(dtl) == .eq) return v; 1754 }; 1755 1756 var best: ?*const VersionInfo = null; 1757 const want_prerelease = constraint.version.prerelease != null; 1758 1759 for (metadata.versions.items) |*v| { 1760 if (v.version.prerelease != null and !want_prerelease) continue; 1761 if (constraint.satisfies(v.version)) { 1762 if (best == null or v.version.order(best.?.version) == .gt) best = v; 1763 } 1764 } 1765 1766 if (best != null or constraint.kind != .any) return best; 1767 1768 for (metadata.versions.items) |*v| { 1769 if (!constraint.satisfies(v.version)) continue; 1770 if (best == null or v.version.order(best.?.version) == .gt) best = v; 1771 } 1772 1773 return best; 1774 } 1775 1776 fn selectBestVersionForConstraints(_: *Resolver, metadata: *const PackageMetadata, all_constraints: []const Constraint) ?*const VersionInfo { 1777 var best: ?*const VersionInfo = null; 1778 1779 var want_prerelease = false; 1780 var all_any = true; 1781 for (all_constraints) |c| { 1782 if (c.version.prerelease != null) want_prerelease = true; 1783 if (c.kind != .any) all_any = false; 1784 } 1785 1786 if (all_any) for (metadata.versions.items) |*v| { 1787 const dtl = metadata.dist_tag_latest orelse break; 1788 if (v.version.order(dtl) == .eq) return v; 1789 }; 1790 1791 for (metadata.versions.items) |*v| { 1792 if (v.version.prerelease != null and !want_prerelease) continue; 1793 1794 var satisfies_all = true; 1795 for (all_constraints) |c| { 1796 if (!c.satisfies(v.version)) { 1797 satisfies_all = false; 1798 break; 1799 } 1800 } 1801 1802 if (satisfies_all) { 1803 if (best == null or v.version.order(best.?.version) == .gt) best = v; 1804 } 1805 } 1806 1807 if (best != null or !all_any) return best; 1808 1809 for (metadata.versions.items) |*v| { 1810 var satisfies_all = true; 1811 for (all_constraints) |c| { 1812 if (!c.satisfies(v.version)) { 1813 satisfies_all = false; 1814 break; 1815 } 1816 } 1817 if (satisfies_all and (best == null or v.version.order(best.?.version) == .gt)) best = v; 1818 } 1819 1820 return best; 1821 } 1822 1823 pub fn writeLockfile(self: *Resolver, path: []const u8) !void { 1824 var writer = lockfile.LockfileWriter.init(self.allocator); 1825 defer writer.deinit(); 1826 1827 var pkg_indices = std.StringHashMap(u32).init(self.allocator); 1828 defer { 1829 var key_iter = pkg_indices.keyIterator(); 1830 while (key_iter.next()) |k| self.allocator.free(k.*); 1831 pkg_indices.deinit(); 1832 } 1833 1834 var idx: u32 = 0; 1835 var iter = self.resolved.valueIterator(); 1836 while (iter.next()) |pkg_ptr| { 1837 const pkg = pkg_ptr.*; 1838 const install_path = try pkg.installPath(self.allocator); 1839 try pkg_indices.put(install_path, idx); 1840 idx += 1; 1841 } 1842 1843 iter = self.resolved.valueIterator(); 1844 while (iter.next()) |pkg_ptr| { 1845 const pkg = pkg_ptr.*; 1846 const name_ref = try writer.internString(pkg.name.slice()); 1847 const url_ref = try writer.internString(pkg.tarball_url); 1848 const prerelease_ref = if (pkg.version.prerelease) |pre| 1849 try writer.internString(pre) 1850 else 1851 lockfile.StringRef.empty; 1852 const parent_ref = if (pkg.parent_path) |parent| 1853 try writer.internString(parent) 1854 else 1855 lockfile.StringRef.empty; 1856 1857 const deps_start: u32 = @intCast(writer.dependencies.items.len); 1858 const pkg_install_path = try pkg.installPath(self.allocator); 1859 defer self.allocator.free(pkg_install_path); 1860 1861 var deps_written: u32 = 0; 1862 for (pkg.dependencies.items) |dep| { 1863 const dep_name = dep.name.slice(); 1864 var found_idx = pkg_indices.get(dep_name); 1865 1866 if (found_idx == null) { 1867 const nested_path = std.fmt.allocPrint(self.allocator, "{s}/node_modules/{s}", .{ pkg_install_path, dep_name }) catch continue; 1868 defer self.allocator.free(nested_path); 1869 found_idx = pkg_indices.get(nested_path); 1870 } 1871 1872 if (found_idx) |fi| { 1873 const constraint_ref = try writer.internString(dep.constraint); 1874 try writer.addDependency(.{ 1875 .package_index = fi, 1876 .constraint = constraint_ref, 1877 .flags = .{ 1878 .peer = dep.flags.peer, 1879 .dev = dep.flags.dev, 1880 .optional = dep.flags.optional, 1881 }, 1882 }); 1883 deps_written += 1; 1884 } 1885 } 1886 1887 _ = try writer.addPackage(.{ 1888 .name = name_ref, 1889 .version_major = pkg.version.major, 1890 .version_minor = pkg.version.minor, 1891 .version_patch = pkg.version.patch, 1892 .prerelease = prerelease_ref, 1893 .integrity = pkg.integrity, 1894 .tarball_url = url_ref, 1895 .parent_path = parent_ref, 1896 .deps_start = deps_start, 1897 .deps_count = deps_written, 1898 .flags = .{ 1899 .direct = pkg.direct, 1900 .has_bin = pkg.has_bin, 1901 }, 1902 }); 1903 } 1904 1905 try writer.write(path); 1906 } 1907};