MIRROR: javascript for 馃悳's, a tiny runtime with big ambitions
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};