aboutsummaryrefslogtreecommitdiff
path: root/src/install/install.zig
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <jarred@jarredsumner.com> 2021-12-10 20:30:03 -0800
committerGravatar Jarred Sumner <jarred@jarredsumner.com> 2021-12-16 19:18:51 -0800
commit0e0e325e5b3e3828bbd3ccbf7e8eeebc5936210f (patch)
tree455161e9604042528a056927d06a8b4bd2e37bb1 /src/install/install.zig
parente97106c770e2dadd062b2b398292bf21af178e4e (diff)
downloadbun-0e0e325e5b3e3828bbd3ccbf7e8eeebc5936210f.tar.gz
bun-0e0e325e5b3e3828bbd3ccbf7e8eeebc5936210f.tar.zst
bun-0e0e325e5b3e3828bbd3ccbf7e8eeebc5936210f.zip
[bun install] optionalDependencies override dependencies if both specified
Diffstat (limited to 'src/install/install.zig')
-rw-r--r--src/install/install.zig104
1 files changed, 78 insertions, 26 deletions
diff --git a/src/install/install.zig b/src/install/install.zig
index f2d484457..aad9454f7 100644
--- a/src/install/install.zig
+++ b/src/install/install.zig
@@ -1557,7 +1557,8 @@ pub const Lockfile = struct {
const total_len = dependencies_list.items.len + total_dependencies_count;
std.debug.assert(dependencies_list.items.len == resolutions_list.items.len);
- var dependencies = dependencies_list.items.ptr[dependencies_list.items.len..total_len];
+ var dependencies: []Dependency = dependencies_list.items.ptr[dependencies_list.items.len..total_len];
+ var start_dependencies = dependencies;
const off = @truncate(u32, dependencies_list.items.len);
@@ -1568,9 +1569,23 @@ pub const Lockfile = struct {
if (comptime Environment.isDebug) std.debug.assert(keys.len == version_strings.len);
const is_peer = comptime strings.eqlComptime(group.field, "peer_dependencies");
+ var i: usize = 0;
- for (keys) |key, i| {
+ list: while (i < keys.len) {
+ const key = keys[i];
const version_string_ = version_strings[i];
+
+ // Duplicate peer & dev dependencies are promoted to whichever appeared first
+ // In practice, npm validates this so it shouldn't happen
+ if (comptime group.behavior.isPeer() or group.behavior.isDev()) {
+ for (start_dependencies[0 .. total_dependencies_count - dependencies.len]) |dependency, j| {
+ if (dependency.name_hash == key.hash) {
+ i += 1;
+ continue :list;
+ }
+ }
+ }
+
const name: ExternalString = string_builder.appendWithHash(ExternalString, key.slice(string_buf), key.hash);
const dep_version = string_builder.appendWithHash(String, version_string_.slice(string_buf), version_string_.hash);
const sliced = dep_version.sliced(lockfile.buffers.string_bytes.items);
@@ -1590,10 +1605,25 @@ pub const Lockfile = struct {
) orelse Dependency.Version{},
};
+ // If a dependency appears in both "dependencies" and "optionalDependencies", it is considered optional!
+ if (comptime group.behavior.isOptional()) {
+ for (start_dependencies[0 .. total_dependencies_count - dependencies.len]) |dep, j| {
+ if (dep.name_hash == key.hash) {
+ // https://docs.npmjs.com/cli/v8/configuring-npm/package-json#optionaldependencies
+ // > Entries in optionalDependencies will override entries of the same name in dependencies, so it's usually best to only put in one place.
+ start_dependencies[j] = dep;
+
+ i += 1;
+ continue :list;
+ }
+ }
+ }
+
package.meta.npm_dependency_count += @as(u32, @boolToInt(dependency.version.tag.isNPM()));
dependencies[0] = dependency;
dependencies = dependencies[1..];
+ i += 1;
}
}
@@ -1849,6 +1879,7 @@ pub const Lockfile = struct {
};
}
+ // It is allowed for duplicate dependencies to exist in optionalDependencies and regular dependencies
if (comptime features.check_for_duplicate_dependencies) {
lockfile.scratch.duplicate_checker_map.clearRetainingCapacity();
try lockfile.scratch.duplicate_checker_map.ensureTotalCapacity(total_dependencies_count);
@@ -1857,34 +1888,16 @@ pub const Lockfile = struct {
inline for (dependency_groups) |group| {
if (json.asProperty(group.prop)) |dependencies_q| {
if (dependencies_q.expr.data == .e_object) {
- for (dependencies_q.expr.data.e_object.properties) |item| {
+ const dependency_props: []const JSAst.G.Property = dependencies_q.expr.data.e_object.properties;
+ var i: usize = 0;
+ outer: while (i < dependency_props.len) {
+ const item = dependency_props[i];
+
const name_ = item.key.?.asString(allocator) orelse "";
const version_ = item.value.?.asString(allocator) orelse "";
const external_name = string_builder.append(ExternalString, name_);
- if (comptime features.check_for_duplicate_dependencies) {
- var entry = lockfile.scratch.duplicate_checker_map.getOrPutAssumeCapacity(external_name.hash);
- if (entry.found_existing) {
- var notes = try allocator.alloc(logger.Data, 1);
- notes[0] = logger.Data{
- .text = try std.fmt.allocPrint(lockfile.allocator, "\"{s}\" was originally specified here", .{name_}),
- .location = logger.Location.init_or_nil(&source, source.rangeOfString(entry.value_ptr.*)),
- };
-
- try log.addRangeErrorFmtWithNotes(
- &source,
- source.rangeOfString(item.key.?.loc),
- lockfile.allocator,
- notes,
- "Duplicate dependency: \"{s}\"",
- .{name_},
- );
- }
-
- entry.value_ptr.* = dependencies_q.loc;
- }
-
const external_version = string_builder.append(String, version_);
const sliced = external_version.sliced(
@@ -1897,14 +1910,53 @@ pub const Lockfile = struct {
&sliced,
log,
) orelse Dependency.Version{};
- dependencies[0] = Dependency{
+ const this_dep = Dependency{
.behavior = group.behavior,
.name = external_name.value,
.name_hash = external_name.hash,
.version = dependency_version,
};
+
+ if (comptime features.check_for_duplicate_dependencies) {
+ var entry = lockfile.scratch.duplicate_checker_map.getOrPutAssumeCapacity(external_name.hash);
+ if (entry.found_existing) {
+ // duplicate dependencies are allowed in optionalDependencies
+ if (comptime group.behavior.isOptional()) {
+ for (package_dependencies[0 .. package_dependencies.len - dependencies.len]) |package_dep, j| {
+ if (package_dep.name_hash == this_dep.name_hash) {
+ package_dependencies[j] = this_dep;
+ break;
+ }
+ }
+
+ i += 1;
+ continue :outer;
+ } else {
+ var notes = try allocator.alloc(logger.Data, 1);
+
+ notes[0] = logger.Data{
+ .text = try std.fmt.allocPrint(lockfile.allocator, "\"{s}\" originally specified here", .{name_}),
+ .location = logger.Location.init_or_nil(&source, source.rangeOfString(entry.value_ptr.*)),
+ };
+
+ try log.addRangeErrorFmtWithNotes(
+ &source,
+ source.rangeOfString(item.key.?.loc),
+ lockfile.allocator,
+ notes,
+ "Duplicate dependency: \"{s}\" specified in package.json",
+ .{name_},
+ );
+ }
+ }
+
+ entry.value_ptr.* = item.value.?.loc;
+ }
+
+ dependencies[0] = this_dep;
package.meta.npm_dependency_count += @as(u32, @boolToInt(dependency_version.tag.isNPM()));
dependencies = dependencies[1..];
+ i += 1;
}
}
}