mirror of
https://github.com/oven-sh/bun
synced 2026-02-10 10:58:56 +00:00
698 lines
22 KiB
Zig
698 lines
22 KiB
Zig
const ExternalStringList = @import("./install.zig").ExternalStringList;
|
|
const Semver = @import("./semver.zig");
|
|
const ExternalString = Semver.ExternalString;
|
|
const String = Semver.String;
|
|
const std = @import("std");
|
|
const SlicedString = Semver.SlicedString;
|
|
const PackageNameHash = @import("./install.zig").PackageNameHash;
|
|
const Features = @import("./install.zig").Features;
|
|
const logger = @import("../logger.zig");
|
|
const Dependency = @This();
|
|
const string = @import("../string_types.zig").string;
|
|
const strings = @import("../string_immutable.zig");
|
|
|
|
pub const URI = union(Tag) {
|
|
local: String,
|
|
remote: String,
|
|
|
|
pub fn eql(lhs: URI, rhs: URI, lhs_buf: []const u8, rhs_buf: []const u8) bool {
|
|
if (@as(Tag, lhs) != @as(Tag, rhs)) {
|
|
return false;
|
|
}
|
|
|
|
if (@as(Tag, lhs) == .local) {
|
|
return strings.eql(lhs.local.slice(lhs_buf), rhs.local.slice(rhs_buf));
|
|
} else {
|
|
return strings.eql(lhs.remote.slice(lhs_buf), rhs.remote.slice(rhs_buf));
|
|
}
|
|
}
|
|
|
|
pub const Tag = enum {
|
|
local,
|
|
remote,
|
|
};
|
|
};
|
|
|
|
name_hash: PackageNameHash = 0,
|
|
name: String = String{},
|
|
version: Dependency.Version = Dependency.Version{},
|
|
|
|
/// This is how the dependency is specified in the package.json file.
|
|
/// This allows us to track whether a package originated in any permutation of:
|
|
/// - `dependencies`
|
|
/// - `devDependencies`
|
|
/// - `optionalDependencies`
|
|
/// - `peerDependencies`
|
|
/// Technically, having the same package name specified under multiple fields is invalid
|
|
/// But we don't want to allocate extra arrays for them. So we use a bitfield instead.
|
|
behavior: Behavior = Behavior.uninitialized,
|
|
|
|
/// Sorting order for dependencies is:
|
|
/// 1. [`dependencies`, `devDependencies`, `optionalDependencies`, `peerDependencies`]
|
|
/// 2. name ASC
|
|
/// "name" must be ASC so that later, when we rebuild the lockfile
|
|
/// we insert it back in reverse order without an extra sorting pass
|
|
pub fn isLessThan(string_buf: []const u8, lhs: Dependency, rhs: Dependency) bool {
|
|
const behavior = lhs.behavior.cmp(rhs.behavior);
|
|
if (behavior != .eq) {
|
|
return behavior == .lt;
|
|
}
|
|
|
|
const lhs_name = lhs.name.slice(string_buf);
|
|
const rhs_name = rhs.name.slice(string_buf);
|
|
return strings.cmpStringsAsc(void{}, lhs_name, rhs_name);
|
|
}
|
|
|
|
pub fn count(this: Dependency, buf: []const u8, comptime StringBuilder: type, builder: StringBuilder) void {
|
|
builder.count(this.name.slice(buf));
|
|
builder.count(this.version.literal.slice(buf));
|
|
}
|
|
|
|
pub fn clone(this: Dependency, buf: []const u8, comptime StringBuilder: type, builder: StringBuilder) !Dependency {
|
|
const out_slice = builder.lockfile.buffers.string_bytes.items;
|
|
const new_literal = builder.append(String, this.version.literal.slice(buf));
|
|
const sliced = new_literal.sliced(out_slice);
|
|
|
|
return Dependency{
|
|
.name_hash = this.name_hash,
|
|
.name = builder.append(String, this.name.slice(buf)),
|
|
.version = Dependency.parseWithTag(
|
|
builder.lockfile.allocator,
|
|
new_literal.slice(out_slice),
|
|
this.version.tag,
|
|
&sliced,
|
|
null,
|
|
) orelse Dependency.Version{},
|
|
.behavior = this.behavior,
|
|
};
|
|
}
|
|
|
|
pub const External = [size]u8;
|
|
|
|
const size = @sizeOf(Dependency.Version.External) +
|
|
@sizeOf(PackageNameHash) +
|
|
@sizeOf(Dependency.Behavior) +
|
|
@sizeOf(String);
|
|
|
|
pub const Context = struct {
|
|
allocator: std.mem.Allocator,
|
|
log: *logger.Log,
|
|
buffer: []const u8,
|
|
};
|
|
|
|
pub fn toDependency(
|
|
this: External,
|
|
ctx: Context,
|
|
) Dependency {
|
|
return Dependency{
|
|
.name = String{
|
|
.bytes = this[0..8].*,
|
|
},
|
|
.name_hash = @bitCast(u64, this[8..16].*),
|
|
.behavior = @intToEnum(Dependency.Behavior, this[16]),
|
|
.version = Dependency.Version.toVersion(this[17..this.len].*, ctx),
|
|
};
|
|
}
|
|
|
|
pub fn toExternal(this: Dependency) External {
|
|
var bytes: External = undefined;
|
|
bytes[0..this.name.bytes.len].* = this.name.bytes;
|
|
bytes[8..16].* = @bitCast([8]u8, this.name_hash);
|
|
bytes[16] = @enumToInt(this.behavior);
|
|
bytes[17..bytes.len].* = this.version.toExternal();
|
|
return bytes;
|
|
}
|
|
|
|
pub const Version = struct {
|
|
tag: Dependency.Version.Tag = Dependency.Version.Tag.uninitialized,
|
|
literal: String = String{},
|
|
value: Value = Value{ .uninitialized = void{} },
|
|
|
|
pub const zeroed = Version{};
|
|
|
|
pub fn clone(
|
|
this: Version,
|
|
buf: []const u8,
|
|
comptime StringBuilder: type,
|
|
builder: StringBuilder,
|
|
) !Version {
|
|
return Version{
|
|
.tag = this.tag,
|
|
.literal = builder.append(String, this.literal.slice(buf)),
|
|
.value = try this.value.clone(buf, builder),
|
|
};
|
|
}
|
|
|
|
pub fn isLessThan(string_buf: []const u8, lhs: Dependency.Version, rhs: Dependency.Version) bool {
|
|
std.debug.assert(lhs.tag == rhs.tag);
|
|
return strings.cmpStringsAsc(.{}, lhs.literal.slice(string_buf), rhs.literal.slice(string_buf));
|
|
}
|
|
|
|
pub const External = [9]u8;
|
|
|
|
pub fn toVersion(
|
|
bytes: Version.External,
|
|
ctx: Dependency.Context,
|
|
) Dependency.Version {
|
|
const slice = String{ .bytes = bytes[1..9].* };
|
|
const tag = @intToEnum(Dependency.Version.Tag, bytes[0]);
|
|
const sliced = &slice.sliced(ctx.buffer);
|
|
return Dependency.parseWithTag(
|
|
ctx.allocator,
|
|
sliced.slice,
|
|
tag,
|
|
sliced,
|
|
ctx.log,
|
|
) orelse Dependency.Version.zeroed;
|
|
}
|
|
|
|
pub inline fn toExternal(this: Version) Version.External {
|
|
var bytes: Version.External = undefined;
|
|
bytes[0] = @enumToInt(this.tag);
|
|
bytes[1..9].* = this.literal.bytes;
|
|
return bytes;
|
|
}
|
|
|
|
pub inline fn eql(
|
|
lhs: Version,
|
|
rhs: Version,
|
|
lhs_buf: []const u8,
|
|
rhs_buf: []const u8,
|
|
) bool {
|
|
if (lhs.tag != rhs.tag) {
|
|
return false;
|
|
}
|
|
|
|
return switch (lhs.tag) {
|
|
// if the two versions are identical as strings, it should often be faster to compare that than the actual semver version
|
|
// semver ranges involve a ton of pointer chasing
|
|
.npm => strings.eql(lhs.literal.slice(lhs_buf), rhs.literal.slice(rhs_buf)) or
|
|
lhs.value.npm.eql(rhs.value.npm),
|
|
.folder, .dist_tag => lhs.literal.eql(rhs.literal, lhs_buf, rhs_buf),
|
|
.tarball => lhs.value.tarball.eql(rhs.value.tarball, lhs_buf, rhs_buf),
|
|
.symlink => lhs.value.symlink.eql(rhs.value.symlink, lhs_buf, rhs_buf),
|
|
else => true,
|
|
};
|
|
}
|
|
|
|
pub const Tag = enum(u8) {
|
|
uninitialized = 0,
|
|
|
|
/// Semver range
|
|
npm = 1,
|
|
|
|
/// NPM dist tag, e.g. "latest"
|
|
dist_tag = 2,
|
|
|
|
/// URI to a .tgz or .tar.gz
|
|
tarball = 3,
|
|
|
|
/// Local folder
|
|
folder = 4,
|
|
|
|
/// link:path
|
|
/// https://docs.npmjs.com/cli/v8/commands/npm-link#synopsis
|
|
/// https://stackoverflow.com/questions/51954956/whats-the-difference-between-yarn-link-and-npm-link
|
|
symlink = 5,
|
|
|
|
/// TODO:
|
|
workspace = 6,
|
|
/// TODO:
|
|
git = 7,
|
|
/// TODO:
|
|
github = 8,
|
|
|
|
pub inline fn isNPM(this: Tag) bool {
|
|
return @enumToInt(this) < 3;
|
|
}
|
|
|
|
pub inline fn isGitHubRepoPath(dependency: string) bool {
|
|
var slash_count: u8 = 0;
|
|
|
|
for (dependency) |c| {
|
|
slash_count += @as(u8, @boolToInt(c == '/'));
|
|
if (slash_count > 1 or c == '#') break;
|
|
|
|
// Must be alphanumeric
|
|
switch (c) {
|
|
'\\', '/', 'a'...'z', 'A'...'Z', '0'...'9', '%' => {},
|
|
else => return false,
|
|
}
|
|
}
|
|
|
|
return (slash_count == 1);
|
|
}
|
|
|
|
// this won't work for query string params
|
|
// i'll let someone file an issue before I add that
|
|
pub inline fn isTarball(dependency: string) bool {
|
|
return strings.endsWithComptime(dependency, ".tgz") or strings.endsWithComptime(dependency, ".tar.gz");
|
|
}
|
|
|
|
pub fn infer(dependency: string) Tag {
|
|
switch (dependency[0]) {
|
|
// npm package
|
|
'=', '>', '<', '0'...'9', '^', '*', '|' => return Tag.npm,
|
|
|
|
'.' => return Tag.folder,
|
|
|
|
'~' => {
|
|
|
|
// https://docs.npmjs.com/cli/v8/configuring-npm/package-json#local-paths
|
|
if (dependency.len > 1 and dependency[1] == '/') {
|
|
return Tag.folder;
|
|
}
|
|
|
|
return Tag.npm;
|
|
},
|
|
|
|
'n' => {
|
|
if (dependency.len > 4 and strings.eqlComptimeIgnoreLen(dependency[0..4], "npm:")) {
|
|
return Tag.npm;
|
|
}
|
|
},
|
|
|
|
// MIGHT be semver, might not be.
|
|
'x', 'X' => {
|
|
if (dependency.len == 1) {
|
|
return Tag.npm;
|
|
}
|
|
|
|
if (dependency[1] == '.') {
|
|
return Tag.npm;
|
|
}
|
|
|
|
return .dist_tag;
|
|
},
|
|
|
|
// git://, git@, git+ssh
|
|
'g' => {
|
|
if (strings.eqlComptime(
|
|
dependency[0..@minimum("git://".len, dependency.len)],
|
|
"git://",
|
|
) or strings.eqlComptime(
|
|
dependency[0..@minimum("git@".len, dependency.len)],
|
|
"git@",
|
|
) or strings.eqlComptime(
|
|
dependency[0..@minimum("git+ssh".len, dependency.len)],
|
|
"git+ssh",
|
|
) or strings.eqlComptime(
|
|
dependency[0..@minimum("git+file".len, dependency.len)],
|
|
"git+file",
|
|
) or strings.eqlComptime(
|
|
dependency[0..@minimum("git+http".len, dependency.len)],
|
|
"git+http",
|
|
) or strings.eqlComptime(
|
|
dependency[0..@minimum("git+https".len, dependency.len)],
|
|
"git+https",
|
|
)) {
|
|
return .git;
|
|
}
|
|
|
|
if (strings.eqlComptime(
|
|
dependency[0..@minimum("github".len, dependency.len)],
|
|
"github",
|
|
) or isGitHubRepoPath(dependency)) {
|
|
return .github;
|
|
}
|
|
|
|
return .dist_tag;
|
|
},
|
|
|
|
'/' => {
|
|
if (isTarball(dependency)) {
|
|
return .tarball;
|
|
}
|
|
|
|
return .folder;
|
|
},
|
|
|
|
// https://, http://
|
|
'h' => {
|
|
if (isTarball(dependency)) {
|
|
return .tarball;
|
|
}
|
|
|
|
var remainder = dependency;
|
|
if (strings.eqlComptime(
|
|
remainder[0..@minimum("https://".len, remainder.len)],
|
|
"https://",
|
|
)) {
|
|
remainder = remainder["https://".len..];
|
|
}
|
|
|
|
if (strings.eqlComptime(
|
|
remainder[0..@minimum("http://".len, remainder.len)],
|
|
"http://",
|
|
)) {
|
|
remainder = remainder["http://".len..];
|
|
}
|
|
|
|
if (strings.eqlComptime(
|
|
remainder[0..@minimum("github".len, remainder.len)],
|
|
"github",
|
|
) or isGitHubRepoPath(remainder)) {
|
|
return .github;
|
|
}
|
|
|
|
return .dist_tag;
|
|
},
|
|
|
|
// Dependencies can start with v
|
|
// v1.0.0 is the same as 1.0.0
|
|
// However, a github repo or a tarball could start with v
|
|
'v' => {
|
|
if (isTarball(dependency)) {
|
|
return .tarball;
|
|
}
|
|
|
|
if (isGitHubRepoPath(dependency)) {
|
|
return .github;
|
|
}
|
|
|
|
return .npm;
|
|
},
|
|
|
|
// file:
|
|
'f' => {
|
|
if (isTarball(dependency))
|
|
return .tarball;
|
|
|
|
if (strings.eqlComptime(
|
|
dependency[0..@minimum("file:".len, dependency.len)],
|
|
"file:",
|
|
)) {
|
|
return .folder;
|
|
}
|
|
|
|
if (isGitHubRepoPath(dependency)) {
|
|
return .github;
|
|
}
|
|
|
|
return .dist_tag;
|
|
},
|
|
|
|
// link://
|
|
'l' => {
|
|
if (isTarball(dependency))
|
|
return .tarball;
|
|
|
|
if (strings.eqlComptime(
|
|
dependency[0..@minimum("link:".len, dependency.len)],
|
|
"link:",
|
|
)) {
|
|
return .symlink;
|
|
}
|
|
|
|
if (isGitHubRepoPath(dependency)) {
|
|
return .github;
|
|
}
|
|
|
|
return .dist_tag;
|
|
},
|
|
|
|
// workspace://
|
|
'w' => {
|
|
if (strings.eqlComptime(
|
|
dependency[0..@minimum("workspace://".len, dependency.len)],
|
|
"workspace://",
|
|
)) {
|
|
return .workspace;
|
|
}
|
|
|
|
if (isTarball(dependency))
|
|
return .tarball;
|
|
|
|
if (isGitHubRepoPath(dependency)) {
|
|
return .github;
|
|
}
|
|
|
|
return .dist_tag;
|
|
},
|
|
|
|
else => {},
|
|
}
|
|
|
|
if (isTarball(dependency))
|
|
return .tarball;
|
|
|
|
if (isGitHubRepoPath(dependency)) {
|
|
return .github;
|
|
}
|
|
|
|
return .dist_tag;
|
|
}
|
|
};
|
|
|
|
pub const Value = union {
|
|
uninitialized: void,
|
|
|
|
npm: Semver.Query.Group,
|
|
dist_tag: String,
|
|
tarball: URI,
|
|
folder: String,
|
|
|
|
/// Equivalent to npm link
|
|
symlink: String,
|
|
|
|
/// Unsupported, but still parsed so an error can be thrown
|
|
workspace: void,
|
|
/// Unsupported, but still parsed so an error can be thrown
|
|
git: void,
|
|
/// Unsupported, but still parsed so an error can be thrown
|
|
github: void,
|
|
};
|
|
};
|
|
|
|
pub fn eql(
|
|
a: Dependency,
|
|
b: Dependency,
|
|
lhs_buf: []const u8,
|
|
rhs_buf: []const u8,
|
|
) bool {
|
|
return a.name_hash == b.name_hash and a.name.len() == b.name.len() and a.version.eql(b.version, lhs_buf, rhs_buf);
|
|
}
|
|
|
|
pub fn eqlResolved(a: Dependency, b: Dependency) bool {
|
|
if (a.isNPM() and b.tag.isNPM()) {
|
|
return a.resolution == b.resolution;
|
|
}
|
|
|
|
return @as(Dependency.Version.Tag, a.version) == @as(Dependency.Version.Tag, b.version) and a.resolution == b.resolution;
|
|
}
|
|
|
|
pub fn parse(
|
|
allocator: std.mem.Allocator,
|
|
dependency_: string,
|
|
sliced: *const SlicedString,
|
|
log: ?*logger.Log,
|
|
) ?Version {
|
|
var dependency = std.mem.trimLeft(u8, dependency_, " \t\n\r");
|
|
|
|
if (dependency.len == 0) return null;
|
|
const tag = Version.Tag.infer(dependency);
|
|
|
|
if (tag == .npm and dependency.len > 4 and strings.eqlComptimeIgnoreLen(dependency[0..4], "npm:")) {
|
|
dependency = dependency[4..];
|
|
}
|
|
|
|
// Strip single leading v
|
|
// v1.0.0 -> 1.0.0
|
|
// note: "vx" is valid, it becomes "x". "yarn add react@vx" -> "yarn add react@x" -> "yarn add react@17.0.2"
|
|
if (tag == .npm and dependency.len > 1 and dependency[0] == 'v') {
|
|
dependency = dependency[1..];
|
|
}
|
|
|
|
return parseWithTag(
|
|
allocator,
|
|
dependency,
|
|
tag,
|
|
sliced,
|
|
log,
|
|
);
|
|
}
|
|
|
|
pub fn parseWithTag(
|
|
allocator: std.mem.Allocator,
|
|
dependency: string,
|
|
tag: Dependency.Version.Tag,
|
|
sliced: *const SlicedString,
|
|
log_: ?*logger.Log,
|
|
) ?Version {
|
|
switch (tag) {
|
|
.npm => {
|
|
const version = Semver.Query.parse(
|
|
allocator,
|
|
dependency,
|
|
sliced.sub(dependency),
|
|
) catch |err| {
|
|
if (log_) |log| log.addErrorFmt(null, logger.Loc.Empty, allocator, "{s} parsing dependency \"{s}\"", .{ @errorName(err), dependency }) catch unreachable;
|
|
return null;
|
|
};
|
|
|
|
return Version{
|
|
.literal = sliced.value(),
|
|
.value = .{ .npm = version },
|
|
.tag = .npm,
|
|
};
|
|
},
|
|
.dist_tag => {
|
|
return Version{
|
|
.literal = sliced.value(),
|
|
.value = .{ .dist_tag = sliced.value() },
|
|
.tag = .dist_tag,
|
|
};
|
|
},
|
|
.tarball => {
|
|
if (strings.hasPrefixComptime(dependency, "https://") or strings.hasPrefixComptime(dependency, "http://")) {
|
|
return Version{
|
|
.tag = .tarball,
|
|
.literal = sliced.value(),
|
|
.value = .{ .tarball = URI{ .remote = sliced.sub(dependency).value() } },
|
|
};
|
|
} else if (strings.hasPrefixComptime(dependency, "file://")) {
|
|
return Version{
|
|
.tag = .tarball,
|
|
.literal = sliced.value(),
|
|
.value = .{ .tarball = URI{ .local = sliced.sub(dependency[7..]).value() } },
|
|
};
|
|
} else if (strings.contains(dependency, "://")) {
|
|
if (log_) |log| log.addErrorFmt(null, logger.Loc.Empty, allocator, "invalid or unsupported dependency \"{s}\"", .{dependency}) catch unreachable;
|
|
return null;
|
|
}
|
|
|
|
return Version{
|
|
.literal = sliced.value(),
|
|
.value = .{
|
|
.tarball = URI{
|
|
.local = sliced.value(),
|
|
},
|
|
},
|
|
.tag = .tarball,
|
|
};
|
|
},
|
|
.folder => {
|
|
if (strings.indexOfChar(dependency, ':')) |protocol| {
|
|
if (strings.eqlComptime(dependency[0..protocol], "file")) {
|
|
if (dependency.len <= protocol) {
|
|
if (log_) |log| log.addErrorFmt(null, logger.Loc.Empty, allocator, "\"file\" dependency missing a path", .{}) catch unreachable;
|
|
return null;
|
|
}
|
|
|
|
return Version{ .literal = sliced.value(), .value = .{ .folder = sliced.sub(dependency[protocol + 1 ..]).value() }, .tag = .folder };
|
|
}
|
|
|
|
if (log_) |log| log.addErrorFmt(null, logger.Loc.Empty, allocator, "Unsupported protocol {s}", .{dependency}) catch unreachable;
|
|
return null;
|
|
}
|
|
|
|
return Version{
|
|
.value = .{ .folder = sliced.value() },
|
|
.tag = .folder,
|
|
.literal = sliced.value(),
|
|
};
|
|
},
|
|
.uninitialized => return null,
|
|
.symlink => {
|
|
if (strings.indexOfChar(dependency, ':')) |colon| {
|
|
return Version{
|
|
.value = .{ .symlink = sliced.sub(dependency[colon + 1 ..]).value() },
|
|
.tag = .symlink,
|
|
.literal = sliced.value(),
|
|
};
|
|
}
|
|
|
|
return Version{
|
|
.value = .{ .symlink = sliced.value() },
|
|
.tag = .symlink,
|
|
.literal = sliced.value(),
|
|
};
|
|
},
|
|
.workspace, .git, .github => {
|
|
if (log_) |log| log.addErrorFmt(null, logger.Loc.Empty, allocator, "Unsupported dependency type {s} for \"{s}\"", .{ @tagName(tag), dependency }) catch unreachable;
|
|
return null;
|
|
},
|
|
}
|
|
}
|
|
|
|
pub const Behavior = enum(u8) {
|
|
uninitialized = 0,
|
|
_,
|
|
|
|
pub const normal: u8 = 1 << 1;
|
|
pub const optional: u8 = 1 << 2;
|
|
pub const dev: u8 = 1 << 3;
|
|
pub const peer: u8 = 1 << 4;
|
|
|
|
pub inline fn isOptional(this: Behavior) bool {
|
|
return (@enumToInt(this) & Behavior.optional) != 0 and !this.isPeer();
|
|
}
|
|
|
|
pub inline fn isDev(this: Behavior) bool {
|
|
return (@enumToInt(this) & Behavior.dev) != 0;
|
|
}
|
|
|
|
pub inline fn isPeer(this: Behavior) bool {
|
|
return (@enumToInt(this) & Behavior.peer) != 0;
|
|
}
|
|
|
|
pub inline fn isNormal(this: Behavior) bool {
|
|
return (@enumToInt(this) & Behavior.normal) != 0;
|
|
}
|
|
|
|
pub inline fn setOptional(this: Behavior, value: bool) Behavior {
|
|
return @intToEnum(Behavior, @enumToInt(this) | (@as(u8, @boolToInt(value))) << 2);
|
|
}
|
|
|
|
pub inline fn setDev(this: Behavior, value: bool) Behavior {
|
|
return @intToEnum(Behavior, @enumToInt(this) | (@as(u8, @boolToInt(value))) << 2);
|
|
}
|
|
|
|
pub inline fn cmp(lhs: Behavior, rhs: Behavior) std.math.Order {
|
|
if (@enumToInt(lhs) == @enumToInt(rhs)) {
|
|
return .eq;
|
|
}
|
|
|
|
if (lhs.isNormal() != rhs.isNormal()) {
|
|
return if (lhs.isNormal())
|
|
.gt
|
|
else
|
|
.lt;
|
|
}
|
|
|
|
if (lhs.isDev() != rhs.isDev()) {
|
|
return if (lhs.isDev())
|
|
.gt
|
|
else
|
|
.lt;
|
|
}
|
|
|
|
if (lhs.isOptional() != rhs.isOptional()) {
|
|
return if (lhs.isOptional())
|
|
.gt
|
|
else
|
|
.lt;
|
|
}
|
|
|
|
if (lhs.isPeer() != rhs.isPeer()) {
|
|
return if (lhs.isPeer())
|
|
.gt
|
|
else
|
|
.lt;
|
|
}
|
|
|
|
return .eq;
|
|
}
|
|
|
|
pub inline fn isRequired(this: Behavior) bool {
|
|
return !isOptional(this);
|
|
}
|
|
|
|
pub fn isEnabled(this: Behavior, features: Features) bool {
|
|
return this.isNormal() or
|
|
(features.dev_dependencies and this.isDev()) or
|
|
(features.peer_dependencies and this.isPeer()) or
|
|
(features.optional_dependencies and this.isOptional());
|
|
}
|
|
};
|