From f7f6233ea85867da681a910d3814954c84bba82f Mon Sep 17 00:00:00 2001 From: Dylan Conway <35280289+dylan-conway@users.noreply.github.com> Date: Wed, 8 Nov 2023 03:36:17 -0800 Subject: [PATCH] fix semver edge cases and buffer used for prerelease comparisons (#6981) * use different buf for group and manifest versions * tests * tests for installs that should fail * allow `<= 152 bytes .../prereleases-3-5.0.0-alpha.151.tgz | Bin 0 -> 152 bytes .../prereleases-3-5.0.0-alpha.152.tgz | Bin 0 -> 153 bytes .../prereleases-3-5.0.0-alpha.153.tgz | Bin 0 -> 153 bytes .../packages/prereleases-4/package.json | 38 + .../prereleases-4-2.0.0-pre.0.tgz | Bin 0 -> 149 bytes test/cli/install/semver.test.ts | 670 ++++++++++++++++++ 19 files changed, 1416 insertions(+), 190 deletions(-) create mode 100644 test/cli/install/registry/packages/prereleases-3/package.json create mode 100644 test/cli/install/registry/packages/prereleases-3/prereleases-3-5.0.0-alpha.150.tgz create mode 100644 test/cli/install/registry/packages/prereleases-3/prereleases-3-5.0.0-alpha.151.tgz create mode 100644 test/cli/install/registry/packages/prereleases-3/prereleases-3-5.0.0-alpha.152.tgz create mode 100644 test/cli/install/registry/packages/prereleases-3/prereleases-3-5.0.0-alpha.153.tgz create mode 100644 test/cli/install/registry/packages/prereleases-4/package.json create mode 100644 test/cli/install/registry/packages/prereleases-4/prereleases-4-2.0.0-pre.0.tgz create mode 100644 test/cli/install/semver.test.ts diff --git a/packages/bun-types/bun.d.ts b/packages/bun-types/bun.d.ts index 0c80cf7ae4..55957076e8 100644 --- a/packages/bun-types/bun.d.ts +++ b/packages/bun-types/bun.d.ts @@ -2576,6 +2576,22 @@ declare module "bun" { */ const stdin: BunFile; + type StringLike = string | { toString(): string }; + + interface semver { + /** + * Test if the version satisfies the range. Stringifies both arguments. Returns `true` or `false`. + */ + satisfies(version: StringLike, range: StringLike): boolean; + + /** + * Returns 0 if the versions are equal, 1 if `v1` is greater, or -1 if `v2` is greater. + * Throws an error if either version is invalid. + */ + order(v1: StringLike, v2: StringLike): -1 | 0 | 1; + } + export const semver: semver; + interface unsafe { /** * Cast bytes to a `String` without copying. This is the fastest way to get a `String` from a `Uint8Array` or `ArrayBuffer`. diff --git a/src/bun.js/api/bun.zig b/src/bun.js/api/bun.zig index 317203c863..b51ed1c004 100644 --- a/src/bun.js/api/bun.zig +++ b/src/bun.js/api/bun.zig @@ -69,6 +69,7 @@ pub const BunObject = struct { pub const stdin = Bun.getStdin; pub const stdout = Bun.getStdout; pub const unsafe = Bun.getUnsafe; + pub const semver = Bun.getSemver; // --- Getters --- fn getterName(comptime baseName: anytype) [:0]const u8 { @@ -114,6 +115,7 @@ pub const BunObject = struct { @export(BunObject.stdin, .{ .name = getterName("stdin") }); @export(BunObject.stdout, .{ .name = getterName("stdout") }); @export(BunObject.unsafe, .{ .name = getterName("unsafe") }); + @export(BunObject.semver, .{ .name = getterName("semver") }); // --- Getters -- // -- Callbacks -- @@ -234,6 +236,7 @@ const ErrorableString = JSC.ErrorableString; const is_bindgen = JSC.is_bindgen; const max_addressible_memory = std.math.maxInt(u56); const Async = bun.Async; +const SemverObject = @import("../../install/semver.zig").SemverObject; threadlocal var css_imports_list_strings: [512]ZigString = undefined; threadlocal var css_imports_list: [512]Api.StringPointer = undefined; @@ -2964,6 +2967,13 @@ pub fn getTOMLObject( return TOMLObject.create(globalThis); } +pub fn getSemver( + globalThis: *JSC.JSGlobalObject, + _: *JSC.JSObject, +) callconv(.C) JSC.JSValue { + return SemverObject.create(globalThis); +} + pub fn getUnsafe( globalThis: *JSC.JSGlobalObject, _: *JSC.JSObject, diff --git a/src/bun.js/bindings/BunObject+exports.h b/src/bun.js/bindings/BunObject+exports.h index b805b90d85..a05744d9e7 100644 --- a/src/bun.js/bindings/BunObject+exports.h +++ b/src/bun.js/bindings/BunObject+exports.h @@ -27,6 +27,7 @@ macro(stdin) \ macro(stdout) \ macro(unsafe) \ + macro(semver) \ // --- Callbacks --- #define FOR_EACH_CALLBACK(macro) \ diff --git a/src/bun.js/bindings/BunObject.cpp b/src/bun.js/bindings/BunObject.cpp index 0bc5a8fb5f..2ee0f5f53e 100644 --- a/src/bun.js/bindings/BunObject.cpp +++ b/src/bun.js/bindings/BunObject.cpp @@ -628,6 +628,7 @@ JSC_DEFINE_HOST_FUNCTION(functionHashCode, resolve BunObject_callback_resolve DontDelete|Function 1 resolveSync BunObject_callback_resolveSync DontDelete|Function 1 revision constructBunRevision ReadOnly|DontDelete|PropertyCallback + semver BunObject_getter_wrap_semver ReadOnly|DontDelete|PropertyCallback serve BunObject_callback_serve DontDelete|Function 1 sha BunObject_callback_sha DontDelete|Function 1 shrink BunObject_callback_shrink DontDelete|Function 1 diff --git a/src/install/install.zig b/src/install/install.zig index 0fc53e595a..b4650cb75f 100644 --- a/src/install/install.zig +++ b/src/install/install.zig @@ -243,6 +243,8 @@ const NetworkTask = struct { header_builder.count("npm-auth-type", "legacy"); } + // The first time this happened! + // // "peerDependencies": { // "@ianvs/prettier-plugin-sort-imports": "*", // "prettier-plugin-twig-melody": "*" @@ -254,7 +256,19 @@ const NetworkTask = struct { // Example case ^ // `@ianvs/prettier-plugin-sort-imports` is peer and also optional but was not marked optional because // the offset would be 0 and the current loop index is also 0. - const invalidate_manifest_cache_because_optional_peer_dependencies_were_not_marked_as_optional_if_the_optional_peer_dependency_offset_was_equal_to_the_current_index = 1697871350; + // const invalidate_manifest_cache_because_optional_peer_dependencies_were_not_marked_as_optional_if_the_optional_peer_dependency_offset_was_equal_to_the_current_index = 1697871350; + // ---- + // The second time this happened! + // + // pre-release sorting when the number of segments between dots were different, was sorted incorrectly + // so we must invalidate the manifest cache once again. + // + // example: + // + // 1.0.0-pre.a.b > 1.0.0-pre.a + // before ordering said the left was smaller than the right + // + const invalidate_manifest_cache_because_prerelease_segments_were_sorted_incorrectly_sometimes = 1697871350; pub fn forManifest( this: *NetworkTask, @@ -304,7 +318,7 @@ const NetworkTask = struct { var last_modified: string = ""; var etag: string = ""; if (loaded_manifest) |manifest| { - if (manifest.pkg.public_max_age > invalidate_manifest_cache_because_optional_peer_dependencies_were_not_marked_as_optional_if_the_optional_peer_dependency_offset_was_equal_to_the_current_index) { + if (manifest.pkg.public_max_age > invalidate_manifest_cache_because_prerelease_segments_were_sorted_incorrectly_sometimes) { last_modified = manifest.pkg.last_modified.slice(manifest.string_buf); etag = manifest.pkg.etag.slice(manifest.string_buf); } @@ -2539,7 +2553,7 @@ pub const PackageManager = struct { Semver.Version.sortGt, ); for (installed_versions.items) |installed_version| { - if (version.value.npm.version.satisfies(installed_version, this.lockfile.buffers.string_bytes.items)) { + if (version.value.npm.version.satisfies(installed_version, this.lockfile.buffers.string_bytes.items, tags_buf.items)) { var buf: [bun.MAX_PATH_BYTES]u8 = undefined; var npm_package_path = this.pathForCachedNPMPath(&buf, package_name, installed_version) catch |err| { Output.debug("error getting path for cached npm path: {s}", .{bun.span(@errorName(err))}); @@ -2751,7 +2765,7 @@ pub const PackageManager = struct { fn resolutionSatisfiesDependency(this: *PackageManager, resolution: Resolution, dependency: Dependency.Version) bool { const buf = this.lockfile.buffers.string_bytes.items; if (resolution.tag == .npm and dependency.tag == .npm) { - return dependency.value.npm.version.satisfies(resolution.value.npm.version, buf); + return dependency.value.npm.version.satisfies(resolution.value.npm.version, buf, buf); } if (resolution.tag == .git and dependency.tag == .git) { @@ -2866,7 +2880,7 @@ pub const PackageManager = struct { if (this.lockfile.workspace_versions.count() > 0) resolve_from_workspace: { if (this.lockfile.workspace_versions.get(name_hash)) |workspace_version| { const buf = this.lockfile.buffers.string_bytes.items; - if (version.value.npm.version.satisfies(workspace_version, buf)) { + if (version.value.npm.version.satisfies(workspace_version, buf, buf)) { const root_package = this.lockfile.rootPackage() orelse break :resolve_from_workspace; const root_dependencies = root_package.dependencies.get(this.lockfile.buffers.dependencies.items); const root_resolutions = root_package.resolutions.get(this.lockfile.buffers.resolutions.items); @@ -2890,7 +2904,7 @@ pub const PackageManager = struct { const manifest = this.manifests.getPtr(name_hash) orelse return null; // manifest might still be downloading. This feels unreliable. const find_result: Npm.PackageManifest.FindResult = switch (version.tag) { .dist_tag => manifest.findByDistTag(this.lockfile.str(&version.value.dist_tag.tag)), - .npm => manifest.findBestVersion(version.value.npm.version), + .npm => manifest.findBestVersion(version.value.npm.version, this.lockfile.buffers.string_bytes.items), else => unreachable, } orelse return if (behavior.isPeer()) null else switch (version.tag) { .npm => error.NoMatchingVersion, @@ -3222,7 +3236,7 @@ pub const PackageManager = struct { while (curr_list) |queries| { var curr: ?*const Semver.Query = &queries.head; while (curr) |query| { - if (group.satisfies(query.range.left.version, buf) or group.satisfies(query.range.right.version, buf)) { + if (group.satisfies(query.range.left.version, buf, buf) or group.satisfies(query.range.right.version, buf, buf)) { name = aliased.value.npm.name; name_hash = String.Builder.stringHash(this.lockfile.str(&name)); break :version aliased; diff --git a/src/install/lockfile.zig b/src/install/lockfile.zig index 7ce33105cd..9167dc611f 100644 --- a/src/install/lockfile.zig +++ b/src/install/lockfile.zig @@ -1686,7 +1686,7 @@ pub fn getPackageID( } if (npm_version) |range| { - if (range.satisfies(resolutions[id].value.npm.version, buf)) return id; + if (range.satisfies(resolutions[id].value.npm.version, buf, buf)) return id; } }, .PackageIDMultiple => |ids| { @@ -1698,7 +1698,7 @@ pub fn getPackageID( } if (npm_version) |range| { - if (range.satisfies(resolutions[id].value.npm.version, buf)) return id; + if (range.satisfies(resolutions[id].value.npm.version, buf, buf)) return id; } } }, @@ -3130,7 +3130,7 @@ pub const Package = extern struct { .npm => if (comptime tag != null) unreachable else if (workspace_version) |ver| { - if (dependency_version.value.npm.version.satisfies(ver, buf)) { + if (dependency_version.value.npm.version.satisfies(ver, buf, buf)) { for (package_dependencies[0..dependencies_count]) |dep| { // `dependencies` & `workspaces` defined within the same `package.json` if (dep.version.tag == .workspace and dep.name_hash == name_hash) { @@ -3155,7 +3155,7 @@ pub const Package = extern struct { .workspace => if (workspace_path) |path| { if (workspace_range) |range| { if (workspace_version) |ver| { - if (range.satisfies(ver, buf)) { + if (range.satisfies(ver, buf, buf)) { dependency_version.literal = path; dependency_version.value.workspace = path; } @@ -3211,7 +3211,7 @@ pub const Package = extern struct { if (switch (package_dep.version.tag) { // `dependencies` & `workspaces` defined within the same `package.json` .npm => String.Builder.stringHash(package_dep.realname().slice(buf)) == name_hash and - package_dep.version.value.npm.version.satisfies(ver, buf), + package_dep.version.value.npm.version.satisfies(ver, buf, buf), // `workspace:*` .workspace => workspace_entry.found_existing and String.Builder.stringHash(package_dep.realname().slice(buf)) == name_hash, @@ -5115,7 +5115,7 @@ pub fn resolve(this: *Lockfile, package_name: []const u8, version: Dependency.Ve const resolutions = this.packages.items(.resolution); if (comptime Environment.allow_assert) std.debug.assert(id < resolutions.len); - if (version.value.npm.version.satisfies(resolutions[id].value.npm.version, buf)) { + if (version.value.npm.version.satisfies(resolutions[id].value.npm.version, buf, buf)) { return id; } }, @@ -5124,7 +5124,7 @@ pub fn resolve(this: *Lockfile, package_name: []const u8, version: Dependency.Ve for (ids.items) |id| { if (comptime Environment.allow_assert) std.debug.assert(id < resolutions.len); - if (version.value.npm.version.satisfies(resolutions[id].value.npm.version, buf)) { + if (version.value.npm.version.satisfies(resolutions[id].value.npm.version, buf, buf)) { return id; } } diff --git a/src/install/npm.zig b/src/install/npm.zig index fdf66f333b..49ce8fff4b 100644 --- a/src/install/npm.zig +++ b/src/install/npm.zig @@ -768,7 +768,7 @@ pub const PackageManifest = struct { version, version, )) catch return null; - return this.findBestVersion(group); + return this.findBestVersion(group, version); }, .dist_tag => { return this.findByDistTag(version); @@ -801,7 +801,7 @@ pub const PackageManifest = struct { return null; } - pub fn findBestVersion(this: *const PackageManifest, group: Semver.Query.Group) ?FindResult { + pub fn findBestVersion(this: *const PackageManifest, group: Semver.Query.Group, group_buf: string) ?FindResult { const left = group.head.head.range.left; // Fast path: exact version if (left.op == .eql) { @@ -809,9 +809,9 @@ pub const PackageManifest = struct { } if (this.findByDistTag("latest")) |result| { - if (group.satisfies(result.version, this.string_buf)) { + if (group.satisfies(result.version, group_buf, this.string_buf)) { if (group.flags.isSet(Semver.Query.Group.Flags.pre)) { - if (left.version.order(result.version, this.string_buf, this.string_buf) == .eq) { + if (left.version.order(result.version, group_buf, this.string_buf) == .eq) { // if prerelease, use latest if semver+tag match range exactly return result; } @@ -829,8 +829,11 @@ pub const PackageManifest = struct { while (i > 0) : (i -= 1) { const version = releases[i - 1]; - if (group.satisfies(version, this.string_buf)) { - return .{ .version = version, .package = &this.pkg.releases.values.get(this.package_versions)[i - 1] }; + if (group.satisfies(version, group_buf, this.string_buf)) { + return .{ + .version = version, + .package = &this.pkg.releases.values.get(this.package_versions)[i - 1], + }; } } } @@ -842,9 +845,12 @@ pub const PackageManifest = struct { const version = prereleases[i - 1]; // This list is sorted at serialization time. - if (group.satisfies(version, this.string_buf)) { + if (group.satisfies(version, group_buf, this.string_buf)) { const packages = this.pkg.prereleases.values.get(this.package_versions); - return .{ .version = version, .package = &packages[i - 1] }; + return .{ + .version = version, + .package = &packages[i - 1], + }; } } } diff --git a/src/install/semver.zig b/src/install/semver.zig index 85d67d81f9..7e62d029d3 100644 --- a/src/install/semver.zig +++ b/src/install/semver.zig @@ -10,6 +10,7 @@ const MutableString = bun.MutableString; const stringZ = bun.stringZ; const default_allocator = bun.default_allocator; const C = bun.C; +const JSC = bun.JSC; const IdentityContext = @import("../identity_context.zig").IdentityContext; /// String type that stores either an offset/length into an external buffer or a string inline directly @@ -763,6 +764,18 @@ pub const Version = extern struct { return lhs.tag.order(rhs.tag, lhs_buf, rhs_buf); } + pub fn orderWithoutBuild( + lhs: Version, + rhs: Version, + lhs_buf: []const u8, + rhs_buf: []const u8, + ) std.math.Order { + const order_without_tag = orderWithoutTag(lhs, rhs); + if (order_without_tag != .eq) return order_without_tag; + + return lhs.tag.orderWithoutBuild(rhs.tag, lhs_buf, rhs_buf); + } + pub const Tag = extern struct { pre: ExternalString = ExternalString{}, build: ExternalString = ExternalString{}, @@ -786,9 +799,11 @@ pub const Version = extern struct { if (lhs_part == null and rhs_part == null) return .eq; - // not having a prerelease part is greater than having one - if (lhs_part == null) return .gt; - if (rhs_part == null) return .lt; + // if right is null, left is greater than. + if (rhs_part == null) return .gt; + + // if left is null, left is less than. + if (lhs_part == null) return .lt; const lhs_uint: ?u32 = std.fmt.parseUnsigned(u32, lhs_part.?, 10) catch null; const rhs_uint: ?u32 = std.fmt.parseUnsigned(u32, rhs_part.?, 10) catch null; @@ -812,7 +827,12 @@ pub const Version = extern struct { unreachable; } - pub fn order(lhs: Tag, rhs: Tag, lhs_buf: []const u8, rhs_buf: []const u8) std.math.Order { + pub fn order( + lhs: Tag, + rhs: Tag, + lhs_buf: []const u8, + rhs_buf: []const u8, + ) std.math.Order { if (!lhs.pre.isEmpty() and !rhs.pre.isEmpty()) { return lhs.orderPre(rhs, lhs_buf, rhs_buf); } @@ -823,6 +843,19 @@ pub const Version = extern struct { return lhs.build.order(&rhs.build, lhs_buf, rhs_buf); } + pub fn orderWithoutBuild( + lhs: Tag, + rhs: Tag, + lhs_buf: []const u8, + rhs_buf: []const u8, + ) std.math.Order { + if (!lhs.pre.isEmpty() and !rhs.pre.isEmpty()) { + return lhs.orderPre(rhs, lhs_buf, rhs_buf); + } + + return lhs.pre.order(&rhs.pre, lhs_buf, rhs_buf); + } + pub fn cloneInto(this: Tag, slice: []const u8, buf: *[]u8) Tag { var pre: String = this.pre.value; var build: String = this.build.value; @@ -1011,6 +1044,22 @@ pub const Version = extern struct { var i: usize = 0; + i += strings.lengthOfLeadingWhitespaceASCII(input[i..]); + if (i == input.len) { + result.valid = false; + return result; + } + + if (input[i] == 'v' or input[i] == '=') { + i += 1; + } + + i += strings.lengthOfLeadingWhitespaceASCII(input[i..]); + if (i == input.len) { + result.valid = false; + return result; + } + // two passes :( while (i < input.len) { if (is_done) { @@ -1329,19 +1378,25 @@ pub const Range = struct { return lhs.op == rhs.op and lhs.version.eql(rhs.version); } - pub fn satisfies(this: Comparator, version: Version, include_pre: bool) bool { - const order = version.orderWithoutTag(this.version); + pub fn satisfies( + comparator: Comparator, + version: Version, + comparator_buf: string, + version_buf: string, + include_pre: bool, + ) bool { + const order = version.orderWithoutBuild(comparator.version, version_buf, comparator_buf); return switch (order) { - .eq => switch (this.op) { + .eq => switch (comparator.op) { .lte, .gte, .eql => true, else => false, }, - .gt => switch (this.op) { + .gt => switch (comparator.op) { .gt, .gte => if (!include_pre) false else true, else => false, }, - .lt => switch (this.op) { + .lt => switch (comparator.op) { .lt, .lte => if (!include_pre) false else true, else => false, }, @@ -1349,9 +1404,9 @@ pub const Range = struct { } }; - pub fn satisfies(this: Range, version: Version, string_buf: string) bool { - const has_left = this.hasLeft(); - const has_right = this.hasRight(); + pub fn satisfies(range: Range, version: Version, range_buf: string, version_buf: string) bool { + const has_left = range.hasLeft(); + const has_right = range.hasRight(); if (!has_left) { return true; @@ -1375,93 +1430,24 @@ pub const Range = struct { var include_pre = true; if (version.tag.hasPre()) { if (!has_right) { - if (!this.left.version.tag.hasPre()) { + if (!range.left.version.tag.hasPre()) { include_pre = false; } } else { - if (!this.left.version.tag.hasPre() and !this.right.version.tag.hasPre()) { + if (!range.left.version.tag.hasPre() and !range.right.version.tag.hasPre()) { include_pre = false; } } } - if (!this.left.satisfies(version, include_pre)) { + if (!range.left.satisfies(version, range_buf, version_buf, include_pre)) { return false; } - if (has_right and !this.right.satisfies(version, include_pre)) { + if (has_right and !range.right.satisfies(version, range_buf, version_buf, include_pre)) { return false; } - if (version.tag.hasPre() and this.left.version.tag.hasPre()) { - // make sure strings leading up to first number are the same - const lhs_str = this.left.version.tag.pre.slice(string_buf); - - const rhs_str = if (this.right.version.tag.hasPre()) this.right.version.tag.pre.slice(string_buf) else null; - const has_rhs_pre = rhs_str != null; - const ver_str = version.tag.pre.slice(string_buf); - - var lhs_itr = strings.split(lhs_str, "."); - var rhs_itr = if (has_rhs_pre) strings.split(rhs_str.?, ".") else null; - var ver_itr = strings.split(ver_str, "."); - - while (true) { - const lhs_part = lhs_itr.next(); - const rhs_part = if (has_rhs_pre) rhs_itr.?.next() else null; - const ver_part = ver_itr.next(); - - // it's a match - if (lhs_part == null and ver_part == null and rhs_part == null) return true; - - // parts do not have equal length - if (lhs_part == null or ver_part == null) return false; - if (Environment.allow_assert) { - if (has_rhs_pre) { - std.debug.assert(rhs_part != null); - } - } - - const lhs_uint = std.fmt.parseUnsigned(u32, lhs_part.?, 10) catch null; - const rhs_uint = if (has_rhs_pre) std.fmt.parseUnsigned(u32, rhs_part.?, 10) catch null else null; - const ver_uint = std.fmt.parseUnsigned(u32, ver_part.?, 10) catch null; - - if (lhs_uint != null and ver_uint != null) { - if (has_rhs_pre and rhs_uint == null) return false; - - if (lhs_uint.? <= ver_uint.?) { - if (!has_rhs_pre) continue; - - if (ver_uint.? <= rhs_uint.?) { - // between lhs and rhs - continue; - } - - // is not between lhs and rhs. - return false; - } - - // falls below lhs - return false; - } - - if (lhs_uint != null or ver_uint != null) return false; - if (Environment.allow_assert) { - if (has_rhs_pre) { - std.debug.assert(rhs_uint == null); - } - } - - if (!strings.eqlLong(lhs_part.?, ver_part.?, true)) return false; - if (Environment.allow_assert) { - if (has_rhs_pre) { - std.debug.assert(strings.eqlLong(ver_part.?, rhs_part.?, true)); - } - } - - // continue to next part - } - } - return true; } }; @@ -1493,8 +1479,16 @@ pub const Query = struct { // OR next: ?*List = null, - pub fn satisfies(this: *const List, version: Version, string_buf: string) bool { - return this.head.satisfies(version, string_buf) or (this.next orelse return false).satisfies(version, string_buf); + pub fn satisfies(list: *const List, version: Version, list_buf: string, version_buf: string) bool { + return list.head.satisfies( + version, + list_buf, + version_buf, + ) or (list.next orelse return false).satisfies( + version, + list_buf, + version_buf, + ); } pub fn eql(lhs: *const List, rhs: *const List) bool { @@ -1551,6 +1545,23 @@ pub const Query = struct { } } + pub fn getExactVersion(this: *const Group) ?Version { + const range = this.head.head.range; + if (this.head.next == null and + this.head.head.next == null and + range.hasLeft() and + range.left.op == .eql and + !range.hasRight()) + { + if (comptime Environment.allow_assert) { + std.debug.assert(this.tail == null); + } + return range.left.version; + } + + return null; + } + pub fn from(version: Version) Group { return .{ .allocator = bun.default_allocator, @@ -1619,8 +1630,13 @@ pub const Query = struct { self.tail = new_tail; } - pub inline fn satisfies(this: *const Group, version: Version, string_buf: string) bool { - return this.head.satisfies(version, string_buf); + pub inline fn satisfies( + group: *const Group, + version: Version, + group_buf: string, + version_buf: string, + ) bool { + return group.head.satisfies(version, group_buf, version_buf); } }; @@ -1633,8 +1649,16 @@ pub const Query = struct { return lhs_next.eql(rhs_next); } - pub fn satisfies(this: *const Query, version: Version, string_buf: string) bool { - return this.range.satisfies(version, string_buf) and (this.next orelse return true).satisfies(version, string_buf); + pub fn satisfies(query: *const Query, version: Version, query_buf: string, version_buf: string) bool { + return query.range.satisfies( + version, + query_buf, + version_buf, + ) and (query.next orelse return true).satisfies( + version, + query_buf, + version_buf, + ); } const Token = struct { @@ -1961,20 +1985,22 @@ pub const Query = struct { i += parse_result.stopped_at; const rollback = i; - const had_space = i < input.len and input[i] == ' '; + const maybe_hyphenate = i < input.len and (input[i] == ' ' or input[i] == '-'); // TODO: can we do this without rolling back? - const hyphenate: bool = had_space and possibly_hyphenate: { - i += 1; - while (i < input.len and input[i] == ' ') : (i += 1) {} + const hyphenate: bool = maybe_hyphenate and possibly_hyphenate: { + i += strings.lengthOfLeadingWhitespaceASCII(input[i..]); if (!(i < input.len and input[i] == '-')) break :possibly_hyphenate false; i += 1; - if (!(i < input.len and input[i] == ' ')) break :possibly_hyphenate false; - i += 1; - while (i < input.len and switch (input[i]) { - ' ', 'v', '=' => true, - else => false, - }) : (i += 1) {} + i += strings.lengthOfLeadingWhitespaceASCII(input[i..]); + if (i == input.len) break :possibly_hyphenate false; + if (input[i] == 'v' or input[i] == '=') { + i += 1; + } + if (i == input.len) break :possibly_hyphenate false; + i += strings.lengthOfLeadingWhitespaceASCII(input[i..]); + if (i == input.len) break :possibly_hyphenate false; + if (!(i < input.len and switch (input[i]) { '0'...'9', 'X', 'x', '*' => true, else => false, @@ -1994,13 +2020,13 @@ pub const Query = struct { const range: Range = brk: { switch (second_parsed.wildcard) { .major => { - second_version.major +|= 1; + // "1.0.0 - x" --> ">=1.0.0" break :brk Range{ .left = .{ .op = .gte, .version = version }, - .right = .{ .op = .lte, .version = second_version }, }; }, .minor => { + // "1.0.0 - 1.x" --> ">=1.0.0 < 2.0.0" second_version.major +|= 1; second_version.minor = 0; second_version.patch = 0; @@ -2011,6 +2037,7 @@ pub const Query = struct { }; }, .patch => { + // "1.0.0 - 1.0.x" --> ">=1.0.0 <1.1.0" second_version.minor +|= 1; second_version.patch = 0; @@ -2072,6 +2099,141 @@ pub const Query = struct { } }; +pub const SemverObject = struct { + pub fn create(globalThis: *JSC.JSGlobalObject) JSC.JSValue { + const object = JSC.JSValue.createEmptyObject(globalThis, 2); + + object.put( + globalThis, + JSC.ZigString.static("satisfies"), + JSC.NewFunction( + globalThis, + JSC.ZigString.static("satisfies"), + 2, + SemverObject.satisfies, + false, + ), + ); + + object.put( + globalThis, + JSC.ZigString.static("order"), + JSC.NewFunction( + globalThis, + JSC.ZigString.static("order"), + 2, + SemverObject.order, + false, + ), + ); + + return object; + } + + pub fn order( + globalThis: *JSC.JSGlobalObject, + callFrame: *JSC.CallFrame, + ) callconv(.C) JSC.JSValue { + var arena = std.heap.ArenaAllocator.init(bun.default_allocator); + defer arena.deinit(); + var stack_fallback = std.heap.stackFallback(512, arena.allocator()); + const allocator = stack_fallback.get(); + + const arguments = callFrame.arguments(2).slice(); + if (arguments.len < 2) { + globalThis.throw("Expected two arguments", .{}); + return .zero; + } + + const left_arg = arguments[0]; + const right_arg = arguments[1]; + + const left_string = left_arg.toStringOrNull(globalThis) orelse return JSC.jsNumber(0); + const right_string = right_arg.toStringOrNull(globalThis) orelse return JSC.jsNumber(0); + + const left = left_string.toSlice(globalThis, allocator); + defer left.deinit(); + const right = right_string.toSlice(globalThis, allocator); + defer right.deinit(); + + if (!strings.isAllASCII(left.slice())) return JSC.jsNumber(0); + if (!strings.isAllASCII(right.slice())) return JSC.jsNumber(0); + + const left_result = Version.parse(SlicedString.init(left.slice(), left.slice())); + const right_result = Version.parse(SlicedString.init(right.slice(), right.slice())); + + if (!left_result.valid) { + globalThis.throw("Invalid SemVer: {s}\n", .{left.slice()}); + return .zero; + } + + if (!right_result.valid) { + globalThis.throw("Invalid SemVer: {s}\n", .{right.slice()}); + return .zero; + } + + const left_version = left_result.version.fill(); + const right_version = right_result.version.fill(); + + return switch (left_version.orderWithoutBuild(right_version, left.slice(), right.slice())) { + .eq => JSC.jsNumber(0), + .gt => JSC.jsNumber(1), + .lt => JSC.jsNumber(-1), + }; + } + + pub fn satisfies( + globalThis: *JSC.JSGlobalObject, + callFrame: *JSC.CallFrame, + ) callconv(.C) JSC.JSValue { + var arena = std.heap.ArenaAllocator.init(bun.default_allocator); + defer arena.deinit(); + var stack_fallback = std.heap.stackFallback(512, arena.allocator()); + const allocator = stack_fallback.get(); + + const arguments = callFrame.arguments(2).slice(); + if (arguments.len < 2) { + globalThis.throw("Expected two arguments", .{}); + return .zero; + } + + const left_arg = arguments[0]; + const right_arg = arguments[1]; + + const left_string = left_arg.toStringOrNull(globalThis) orelse return .false; + const right_string = right_arg.toStringOrNull(globalThis) orelse return .false; + + const left = left_string.toSlice(globalThis, allocator); + defer left.deinit(); + const right = right_string.toSlice(globalThis, allocator); + defer right.deinit(); + + if (!strings.isAllASCII(left.slice())) return .false; + if (!strings.isAllASCII(right.slice())) return .false; + + const left_result = Version.parse(SlicedString.init(left.slice(), left.slice())); + if (left_result.wildcard != .none) { + return .false; + } + + const left_version = left_result.version.fill(); + + const right_group = Query.parse( + allocator, + right.slice(), + SlicedString.init(right.slice(), right.slice()), + ) catch return .false; + + const right_version = right_group.getExactVersion(); + + if (right_version != null) { + return JSC.jsBoolean(left_version.eql(right_version.?)); + } + + return JSC.jsBoolean(right_group.satisfies(left_version, right.slice(), left.slice())); + } +}; + const expect = if (Environment.isTest) struct { pub var counter: usize = 0; pub fn isRangeMatch(input: string, version_str: string) bool { diff --git a/src/string_immutable.zig b/src/string_immutable.zig index 86d7d2501f..16bb895eb3 100644 --- a/src/string_immutable.zig +++ b/src/string_immutable.zig @@ -4367,6 +4367,19 @@ pub fn trim(slice: anytype, comptime values_to_strip: []const u8) @TypeOf(slice) return slice[begin..end]; } +pub fn lengthOfLeadingWhitespaceASCII(slice: string) usize { + for (slice) |*c| { + switch (c.*) { + ' ', '\t', '\n', '\r', std.ascii.control_code.vt, std.ascii.control_code.ff => {}, + else => { + return @intFromPtr(c) - @intFromPtr(slice.ptr); + }, + } + } + + return slice.len; +} + pub fn containsNonBmpCodePointUTF16(_text: []const u16) bool { const n = _text.len; if (n > 0) { diff --git a/test/cli/install/registry/bun-install-registry.test.ts b/test/cli/install/registry/bun-install-registry.test.ts index b9deed9b4a..64b8bd4268 100644 --- a/test/cli/install/registry/bun-install-registry.test.ts +++ b/test/cli/install/registry/bun-install-registry.test.ts @@ -382,73 +382,276 @@ describe("semver", () => { }); }); -describe("prereleases", () => { - const prereleaseTests = [ - [ - { title: "specific", depVersion: "1.0.0-future.1", expected: "1.0.0-future.1" }, - { title: "latest", depVersion: "latest", expected: "1.0.0-future.4" }, - { title: "range starting with latest", depVersion: "^1.0.0-future.4", expected: "1.0.0-future.4" }, - { title: "range above latest", depVersion: "^1.0.0-future.5", expected: "1.0.0-future.7" }, - ], - [ - { title: "#6683", depVersion: "^1.0.0-next.23", expected: "1.0.0-next.23" }, - { - title: "greater than or equal to", - depVersion: ">=1.0.0-next.23", - expected: "1.0.0-next.23", - }, - { title: "latest", depVersion: "latest", expected: "0.5.0" }, - { title: "greater than or equal to latest", depVersion: ">=0.5.0", expected: "0.5.0" }, - ], - ]; - for (let i = 0; i < prereleaseTests.length; i++) { - const tests = prereleaseTests[i]; - const depName = `prereleases-${i + 1}`; - describe(`${i}`, () => { - for (const { title, depVersion, expected } of tests) { - test(title, async () => { - await writeFile( - join(packageDir, "package.json"), - JSON.stringify({ - name: "foo", - version: "1.0.0", - dependencies: { - [`${depName}`]: depVersion, - }, - }), - ); +const prereleaseTests = [ + [ + { title: "specific", depVersion: "1.0.0-future.1", expected: "1.0.0-future.1" }, + { title: "latest", depVersion: "latest", expected: "1.0.0-future.4" }, + { title: "range starting with latest", depVersion: "^1.0.0-future.4", expected: "1.0.0-future.4" }, + { title: "range above latest", depVersion: "^1.0.0-future.5", expected: "1.0.0-future.7" }, + ], + [ + { title: "#6683", depVersion: "^1.0.0-next.23", expected: "1.0.0-next.23" }, + { + title: "greater than or equal to", + depVersion: ">=1.0.0-next.23", + expected: "1.0.0-next.23", + }, + { title: "latest", depVersion: "latest", expected: "0.5.0" }, + { title: "greater than or equal to latest", depVersion: ">=0.5.0", expected: "0.5.0" }, + ], - const { stdout, stderr, exited } = spawn({ - cmd: [bunExe(), "install"], - cwd: packageDir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); + // package "prereleases-3" has four versions, all with prerelease tags: + // - 5.0.0-alpha.150 + // - 5.0.0-alpha.151 + // - 5.0.0-alpha.152 + // - 5.0.0-alpha.153 + [ + { title: "#6956", depVersion: "^5.0.0-alpha.153", expected: "5.0.0-alpha.153" }, + { title: "range matches highest possible", depVersion: "^5.0.0-alpha.152", expected: "5.0.0-alpha.153" }, + { title: "exact", depVersion: "5.0.0-alpha.152", expected: "5.0.0-alpha.152" }, + { title: "exact latest", depVersion: "5.0.0-alpha.153", expected: "5.0.0-alpha.153" }, + { title: "latest", depVersion: "latest", expected: "5.0.0-alpha.153" }, + { title: "~ lower than latest", depVersion: "~5.0.0-alpha.151", expected: "5.0.0-alpha.153" }, + { + title: "~ equal semver and lower non-existant prerelease", + depVersion: "~5.0.0-alpha.100", + expected: "5.0.0-alpha.153", + }, + { + title: "^ equal semver and lower non-existant prerelease", + depVersion: "^5.0.0-alpha.100", + expected: "5.0.0-alpha.153", + }, + { + title: "~ and ^ latest prerelease", + depVersion: "~5.0.0-alpha.153 || ^5.0.0-alpha.153", + expected: "5.0.0-alpha.153", + }, + { + title: "< latest prerelease", + depVersion: "<5.0.0-alpha.153", + expected: "5.0.0-alpha.152", + }, + { + title: "< lower than latest prerelease", + depVersion: "<5.0.0-alpha.152", + expected: "5.0.0-alpha.151", + }, + { + title: "< higher than latest prerelease", + depVersion: "<5.0.0-alpha.22343423", + expected: "5.0.0-alpha.153", + }, + { + title: "< at lowest possible version", + depVersion: "<5.0.0-alpha.151", + expected: "5.0.0-alpha.150", + }, + { + title: "<= latest prerelease", + depVersion: "<=5.0.0-alpha.153", + expected: "5.0.0-alpha.153", + }, + { + title: "<= lower than latest prerelease", + depVersion: "<=5.0.0-alpha.152", + expected: "5.0.0-alpha.152", + }, + { + title: "<= lowest possible version", + depVersion: "<=5.0.0-alpha.150", + expected: "5.0.0-alpha.150", + }, + { + title: "<= higher than latest prerelease", + depVersion: "<=5.0.0-alpha.153261345", + expected: "5.0.0-alpha.153", + }, + { + title: "> latest prerelease", + depVersion: ">=5.0.0-alpha.153", + expected: "5.0.0-alpha.153", + }, + ], +]; +for (let i = 0; i < prereleaseTests.length; i++) { + const tests = prereleaseTests[i]; + const depName = `prereleases-${i + 1}`; + describe(`${depName} should pass`, () => { + for (const { title, depVersion, expected } of tests) { + test(title, async () => { + await writeFile( + join(packageDir, "package.json"), + JSON.stringify({ + name: "foo", + version: "1.0.0", + dependencies: { + [`${depName}`]: depVersion, + }, + }), + ); - expect(stderr).toBeDefined(); - const err = await new Response(stderr).text(); - expect(stdout).toBeDefined(); - const out = await new Response(stdout).text(); - expect(err).toContain("Saved lockfile"); - expect(err).not.toContain("not found"); - expect(err).not.toContain("error:"); - expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([ - ` + ${depName}@${expected}`, - "", - " 1 package installed", - ]); - expect(await file(join(packageDir, "node_modules", depName, "package.json")).json()).toEqual({ - name: depName, - version: expected, - } as any); - expect(await exited).toBe(0); + const { stdout, stderr, exited } = spawn({ + cmd: [bunExe(), "install"], + cwd: packageDir, + stdout: null, + stdin: "pipe", + stderr: "pipe", + env, }); - } - }); - } -}); + + expect(stderr).toBeDefined(); + const err = await new Response(stderr).text(); + expect(stdout).toBeDefined(); + const out = await new Response(stdout).text(); + expect(err).toContain("Saved lockfile"); + expect(err).not.toContain("not found"); + expect(err).not.toContain("error:"); + expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([ + ` + ${depName}@${expected}`, + "", + " 1 package installed", + ]); + expect(await file(join(packageDir, "node_modules", depName, "package.json")).json()).toEqual({ + name: depName, + version: expected, + } as any); + expect(await exited).toBe(0); + }); + } + }); +} +const prereleaseFailTests = [ + [ + // { title: "specific", depVersion: "1.0.0-future.1", expected: "1.0.0-future.1" }, + // { title: "latest", depVersion: "latest", expected: "1.0.0-future.4" }, + // { title: "range starting with latest", depVersion: "^1.0.0-future.4", expected: "1.0.0-future.4" }, + // { title: "range above latest", depVersion: "^1.0.0-future.5", expected: "1.0.0-future.7" }, + ], + [ + // { title: "#6683", depVersion: "^1.0.0-next.23", expected: "1.0.0-next.23" }, + // { + // title: "greater than or equal to", + // depVersion: ">=1.0.0-next.23", + // expected: "1.0.0-next.23", + // }, + // { title: "latest", depVersion: "latest", expected: "0.5.0" }, + // { title: "greater than or equal to latest", depVersion: ">=0.5.0", expected: "0.5.0" }, + ], + + // package "prereleases-3" has four versions, all with prerelease tags: + // - 5.0.0-alpha.150 + // - 5.0.0-alpha.151 + // - 5.0.0-alpha.152 + // - 5.0.0-alpha.153 + [ + { + title: "^ with higher non-existant prerelease", + depVersion: "^5.0.0-alpha.1000", + }, + { + title: "~ with higher non-existant prerelease", + depVersion: "~5.0.0-alpha.1000", + }, + { + title: "> with higher non-existant prerelease", + depVersion: ">5.0.0-alpha.1000", + }, + { + title: ">= with higher non-existant prerelease", + depVersion: ">=5.0.0-alpha.1000", + }, + { + title: "^4.3.0", + depVersion: "^4.3.0", + }, + { + title: "~4.3.0", + depVersion: "~4.3.0", + }, + { + title: ">4.3.0", + depVersion: ">4.3.0", + }, + { + title: ">=4.3.0", + depVersion: ">=4.3.0", + }, + { + title: "<5.0.0-alpha.150", + depVersion: "<5.0.0-alpha.150", + }, + { + title: "<=5.0.0-alpha.149", + depVersion: "<=5.0.0-alpha.149", + }, + { + title: "greater than highest prerelease", + depVersion: ">5.0.0-alpha.153", + }, + { + title: "greater than or equal to highest prerelease + 1", + depVersion: ">=5.0.0-alpha.154", + }, + ], + // prereleases-4 has one version + // - 2.0.0-pre.0 + [ + { + title: "wildcard should not match prerelease", + depVersion: "x", + }, + { + title: "major wildcard should not match prerelease", + depVersion: "x.0.0", + }, + { + title: "minor wildcard should not match prerelease", + depVersion: "2.x", + }, + { + title: "patch wildcard should not match prerelease", + depVersion: "2.0.x", + }, + ], +]; +for (let i = 0; i < prereleaseFailTests.length; i++) { + const tests = prereleaseFailTests[i]; + const depName = `prereleases-${i + 1}`; + describe(`${depName} should fail`, () => { + for (const { title, depVersion } of tests) { + test(title, async () => { + await writeFile( + join(packageDir, "package.json"), + JSON.stringify({ + name: "foo", + version: "1.0.0", + dependencies: { + [`${depName}`]: depVersion, + }, + }), + ); + + const { stdout, stderr, exited } = spawn({ + cmd: [bunExe(), "install"], + cwd: packageDir, + stdout: null, + stdin: "pipe", + stderr: "pipe", + env, + }); + + expect(stderr).toBeDefined(); + const err = await new Response(stderr).text(); + expect(stdout).toBeDefined(); + const out = await new Response(stdout).text(); + expect(out).toBeEmpty(); + expect(err).toContain(`No version matching "${depVersion}" found for specifier "${depName}"`); + expect(await exited).toBe(1); + }); + } + }); +} describe("yarn tests", () => { test("dragon test 1", async () => { diff --git a/test/cli/install/registry/packages/.verdaccio-db.json b/test/cli/install/registry/packages/.verdaccio-db.json index fca64c97c5..9174a313a0 100644 --- a/test/cli/install/registry/packages/.verdaccio-db.json +++ b/test/cli/install/registry/packages/.verdaccio-db.json @@ -1 +1 @@ -{"list":["basic-1","@babel/parser","@babel/traverse","@private/has-bin-entry","@private/package","@private/unconventional-tarball","@scoped/create-test-app","@scoped/has-bin-entry","@types/babel__traverse","@types/is-number","@types/no-deps","binding-gyp-scripts","broken-peer-deps","create-test-app","dep-loop-entry","dep-loop-exit","dev-deps","dragon-test-1-a","dragon-test-1-b","dragon-test-1-c","dragon-test-1-d","dragon-test-1-e","dragon-test-3-a","dragon-test-3-b","dragon-test-7-a","dragon-test-7-b","dragon-test-7-c","dragon-test-7-d","dragon-test-8-a","dragon-test-8-b","dragon-test-8-c","dragon-test-8-d","dragon-test-11-a","dragon-test-11-b","fallback-peer-deps","forward-peer-deps","forward-peer-deps-too","has-bin-entries","has-symlinks","has-types","hoisting-peer-check-child","hoisting-peer-check-parent","inject-node-gyp","invalid-main","is-number","left-pad","mismatched-peer-deps-lvl0","mismatched-peer-deps-lvl1","mismatched-peer-deps-lvl2","native","native-bar-x64","native-foo-x64","native-foo-x86","native-libc-glibc","native-libc-musl","no-deps","no-deps-backward-tags","no-deps-bins","no-deps-bins-esm","no-deps-browser-field","no-deps-build-metadata","no-deps-checked","no-deps-deprecated","no-deps-deprecated-empty","no-deps-deprecated-whitespace","no-deps-esm","no-deps-exports","no-deps-failing","no-deps-mjs","no-deps-nested-postinstall","no-deps-scripted","no-deps-scripted-bis","no-deps-scripted-empty","no-deps-scripted-to-fail","no-deps-scripted-to-deeply-fail","no-deps-tags","node-gyp","node-modules-path","one-deep1-dep-bins","one-deep2-dep-bins","one-dep-scripted","one-fixed-dep","one-fixed-dep-bins","one-fixed-dep-checked","one-fixed-dep-scripted","one-fixed-dep-with-types","one-range-dep","one-range-dep-too","optional-native","optional-peer-deps","optional-peer-deps-implicit","path-parse","peer-deps","peer-deps-fixed","peer-deps-lvl0","peer-deps-lvl1","peer-deps-lvl2","peer-deps-too","prefer-unplugged-false","prefer-unplugged-true","private-package","private-unconventional-tarball","provides-peer-deps-1-0-0","provides-peer-deps-1-0-0-too","provides-peer-deps-2-0-0","resolve","self-require-dep","self-require-trap","two-range-deps","unconventional-tarball","various-requires","vulnerable","vulnerable-dep","vulnerable-many","vulnerable-peer-deps","prereleases-1","prereleases-2","dep-with-tags"],"secret":"4f8fdf3ae3425c5661cbdd85990a5f44365b989c3c6f478ed48ccf5bae9e14b1"} \ No newline at end of file +{"list":["basic-1","@babel/parser","@babel/traverse","@private/has-bin-entry","@private/package","@private/unconventional-tarball","@scoped/create-test-app","@scoped/has-bin-entry","@types/babel__traverse","@types/is-number","@types/no-deps","binding-gyp-scripts","broken-peer-deps","create-test-app","dep-loop-entry","dep-loop-exit","dev-deps","dragon-test-1-a","dragon-test-1-b","dragon-test-1-c","dragon-test-1-d","dragon-test-1-e","dragon-test-3-a","dragon-test-3-b","dragon-test-7-a","dragon-test-7-b","dragon-test-7-c","dragon-test-7-d","dragon-test-8-a","dragon-test-8-b","dragon-test-8-c","dragon-test-8-d","dragon-test-11-a","dragon-test-11-b","fallback-peer-deps","forward-peer-deps","forward-peer-deps-too","has-bin-entries","has-symlinks","has-types","hoisting-peer-check-child","hoisting-peer-check-parent","inject-node-gyp","invalid-main","is-number","left-pad","mismatched-peer-deps-lvl0","mismatched-peer-deps-lvl1","mismatched-peer-deps-lvl2","native","native-bar-x64","native-foo-x64","native-foo-x86","native-libc-glibc","native-libc-musl","no-deps","no-deps-backward-tags","no-deps-bins","no-deps-bins-esm","no-deps-browser-field","no-deps-build-metadata","no-deps-checked","no-deps-deprecated","no-deps-deprecated-empty","no-deps-deprecated-whitespace","no-deps-esm","no-deps-exports","no-deps-failing","no-deps-mjs","no-deps-nested-postinstall","no-deps-scripted","no-deps-scripted-bis","no-deps-scripted-empty","no-deps-scripted-to-fail","no-deps-scripted-to-deeply-fail","no-deps-tags","node-gyp","node-modules-path","one-deep1-dep-bins","one-deep2-dep-bins","one-dep-scripted","one-fixed-dep","one-fixed-dep-bins","one-fixed-dep-checked","one-fixed-dep-scripted","one-fixed-dep-with-types","one-range-dep","one-range-dep-too","optional-native","optional-peer-deps","optional-peer-deps-implicit","path-parse","peer-deps","peer-deps-fixed","peer-deps-lvl0","peer-deps-lvl1","peer-deps-lvl2","peer-deps-too","prefer-unplugged-false","prefer-unplugged-true","private-package","private-unconventional-tarball","provides-peer-deps-1-0-0","provides-peer-deps-1-0-0-too","provides-peer-deps-2-0-0","resolve","self-require-dep","self-require-trap","two-range-deps","unconventional-tarball","various-requires","vulnerable","vulnerable-dep","vulnerable-many","vulnerable-peer-deps","prereleases-1","prereleases-2","dep-with-tags","prereleases-3","hexo","prereleases-4"],"secret":"4f8fdf3ae3425c5661cbdd85990a5f44365b989c3c6f478ed48ccf5bae9e14b1"} \ No newline at end of file diff --git a/test/cli/install/registry/packages/prereleases-3/package.json b/test/cli/install/registry/packages/prereleases-3/package.json new file mode 100644 index 0000000000..05dcc44514 --- /dev/null +++ b/test/cli/install/registry/packages/prereleases-3/package.json @@ -0,0 +1,92 @@ +{ + "name": "prereleases-3", + "versions": { + "5.0.0-alpha.150": { + "name": "prereleases-3", + "version": "5.0.0-alpha.150", + "_id": "prereleases-3@5.0.0-alpha.150", + "_nodeVersion": "20.8.0", + "_npmVersion": "10.1.0", + "dist": { + "integrity": "sha512-W6a/CVlIGcsOCUGwRuvvl+VhFy6fvHrfijGcQ9ykL8vvDv+ZOc6wXZ/KH3xyG/7ZJd5pnfoVozKj4Gb5/oRHTg==", + "shasum": "5f87d66f180a8e78b27a09001ba2568be219429e", + "tarball": "http://localhost:4873/prereleases-3/-/prereleases-3-5.0.0-alpha.150.tgz" + }, + "contributors": [] + }, + "5.0.0-alpha.151": { + "name": "prereleases-3", + "version": "5.0.0-alpha.151", + "_id": "prereleases-3@5.0.0-alpha.151", + "_nodeVersion": "20.8.0", + "_npmVersion": "10.1.0", + "dist": { + "integrity": "sha512-Dxzdd+WpspJfPPLrJZ0Xb6C/6Q+32yPu3yxTDraiZra6JUKwL2uBqYFSq4+aY453V8JAIsInqAUoi4yL624fxw==", + "shasum": "e0660f54d9c105a7900fcb94021037d7f1a8eccf", + "tarball": "http://localhost:4873/prereleases-3/-/prereleases-3-5.0.0-alpha.151.tgz" + }, + "contributors": [] + }, + "5.0.0-alpha.152": { + "name": "prereleases-3", + "version": "5.0.0-alpha.152", + "_id": "prereleases-3@5.0.0-alpha.152", + "_nodeVersion": "20.8.0", + "_npmVersion": "10.1.0", + "dist": { + "integrity": "sha512-pTQ2f7DFJQlg++wJ5eTx74XO1vhtn0xcbALUN4XZI9w2vvYAeiEOm5HoBUOU/ZMVEB4EDDU+GjNy+Ht/AxKJtg==", + "shasum": "73b6201c6d0fb958bb4d86e1da44c191a07cda1c", + "tarball": "http://localhost:4873/prereleases-3/-/prereleases-3-5.0.0-alpha.152.tgz" + }, + "contributors": [] + }, + "5.0.0-alpha.153": { + "name": "prereleases-3", + "version": "5.0.0-alpha.153", + "_id": "prereleases-3@5.0.0-alpha.153", + "_nodeVersion": "20.8.0", + "_npmVersion": "10.1.0", + "dist": { + "integrity": "sha512-5rcdlO+6LKETuexXvy1ljm2YdvkGNh/6yqZXG44i67DzKwU11wHxd+iqJsfeZ6H5vIxdMF8f6GbOKvEolc+9cw==", + "shasum": "2a24bf0c596fe7cc03e5d365a5e11fb95d4f9804", + "tarball": "http://localhost:4873/prereleases-3/-/prereleases-3-5.0.0-alpha.153.tgz" + }, + "contributors": [] + } + }, + "time": { + "modified": "2023-11-07T23:35:58.654Z", + "created": "2023-11-07T23:35:49.806Z", + "5.0.0-alpha.150": "2023-11-07T23:35:49.806Z", + "5.0.0-alpha.151": "2023-11-07T23:35:52.818Z", + "5.0.0-alpha.152": "2023-11-07T23:35:55.655Z", + "5.0.0-alpha.153": "2023-11-07T23:35:58.654Z" + }, + "users": {}, + "dist-tags": { + "latest": "5.0.0-alpha.153" + }, + "_uplinks": {}, + "_distfiles": {}, + "_attachments": { + "prereleases-3-5.0.0-alpha.150.tgz": { + "shasum": "5f87d66f180a8e78b27a09001ba2568be219429e", + "version": "5.0.0-alpha.150" + }, + "prereleases-3-5.0.0-alpha.151.tgz": { + "shasum": "e0660f54d9c105a7900fcb94021037d7f1a8eccf", + "version": "5.0.0-alpha.151" + }, + "prereleases-3-5.0.0-alpha.152.tgz": { + "shasum": "73b6201c6d0fb958bb4d86e1da44c191a07cda1c", + "version": "5.0.0-alpha.152" + }, + "prereleases-3-5.0.0-alpha.153.tgz": { + "shasum": "2a24bf0c596fe7cc03e5d365a5e11fb95d4f9804", + "version": "5.0.0-alpha.153" + } + }, + "_rev": "", + "_id": "prereleases-3", + "readme": "ERROR: No README data found!" +} \ No newline at end of file diff --git a/test/cli/install/registry/packages/prereleases-3/prereleases-3-5.0.0-alpha.150.tgz b/test/cli/install/registry/packages/prereleases-3/prereleases-3-5.0.0-alpha.150.tgz new file mode 100644 index 0000000000000000000000000000000000000000..c76b0dd879a4b99dbee8063d1f0cf56cdb7dc880 GIT binary patch literal 152 zcmV;J0B8RniwFP!00002|LxDs3c@f92k@Tv6rpEVQ?^v_ZGr^9W>*^ZBEGwwf+vqd z81#4fha`t2@$SrfS*K6BlaA5M3=u=$!>|6^hQM?b(EAXu?3IEDOPW8p>6LRiWrimU z_~t@98UP#lB6bTlWyvxKt7zNV)ik*^ZBEGwwf+vqd z81#4fha`t2@$SrfS*K6BlaA5M3=u=$!>|6^hQM?b(EAXu?3IEDOPW8p>6LRiWrimU z_~t@98UP#lB6bTlWyvxKt7zNV)ikG<4=R5|rFhuly55M|vTLR_CqqW{+*((JRmQ;Um&o@rxlm(tF z;F~KkH2^yBRrC&YOp;^}&XQZFH}mKrDWAqLPsyOcmWOy`W69~}sj8}~s { + // https://github.com/npm/node-semver/blob/14d263faa156e408a033b9b12a2f87735c2df42c/test/fixtures/comparisons.js#L4 + test("comparisons", () => { + var tests = [ + ["0.0.0", "0.0.0-foo"], + ["0.0.1", "0.0.0"], + ["1.0.0", "0.9.9"], + ["0.10.0", "0.9.0"], + ["0.99.0", "0.10.0"], + ["2.0.0", "1.2.3"], + ["v0.0.0", "0.0.0-foo"], + ["v0.0.1", "0.0.0"], + ["v1.0.0", "0.9.9"], + ["v0.10.0", "0.9.0"], + ["v0.99.0", "0.10.0"], + ["v2.0.0", "1.2.3"], + ["0.0.0", "v0.0.0-foo"], + ["0.0.1", "v0.0.0"], + ["1.0.0", "v0.9.9"], + ["0.10.0", "v0.9.0"], + ["0.99.0", "v0.10.0"], + ["2.0.0", "v1.2.3"], + ["1.2.3", "1.2.3-asdf"], + ["1.2.3", "1.2.3-4"], + ["1.2.3", "1.2.3-4-foo"], + ["1.2.3-5-foo", "1.2.3-5"], + ["1.2.3-5", "1.2.3-4"], + ["1.2.3-5-foo", "1.2.3-5-Foo"], + ["3.0.0", "2.7.2+asdf"], + ["1.2.3-a.10", "1.2.3-a.5"], + ["1.2.3-a.b", "1.2.3-a.5"], + ["1.2.3-a.b", "1.2.3-a"], + ["1.2.3-a.b.c.10.d.5", "1.2.3-a.b.c.5.d.100"], + ["1.2.3-r2", "1.2.3-r100"], + ["1.2.3-r100", "1.2.3-R2"], + ["1.0.0-pre.a.b", "1.0.0-pre.a"], + ]; + for (const [left, right] of tests) { + expect(order(left, right)).toBe(1); + expect(order(right, left)).toBe(-1); + expect(order(left, left)).toBe(0); + expect(order(right, right)).toBe(0); + } + }); + + test("equality", () => { + // https://github.com/npm/node-semver/blob/14d263faa156e408a033b9b12a2f87735c2df42c/test/fixtures/equality.js#L3 + var tests = [ + ["1.2.3", "v1.2.3"], + ["1.2.3", "=1.2.3"], + ["1.2.3", "v 1.2.3"], + ["1.2.3", "= 1.2.3"], + ["1.2.3", " v1.2.3"], + ["1.2.3", " =1.2.3"], + ["1.2.3", " v 1.2.3"], + ["1.2.3", " = 1.2.3"], + ["1.2.3-0", "v1.2.3-0"], + ["1.2.3-0", "=1.2.3-0"], + ["1.2.3-0", "v 1.2.3-0"], + ["1.2.3-0", "= 1.2.3-0"], + ["1.2.3-0", " v1.2.3-0"], + ["1.2.3-0", " =1.2.3-0"], + ["1.2.3-0", " v 1.2.3-0"], + ["1.2.3-0", " = 1.2.3-0"], + ["1.2.3-1", "v1.2.3-1"], + ["1.2.3-1", "=1.2.3-1"], + ["1.2.3-1", "v 1.2.3-1"], + ["1.2.3-1", "= 1.2.3-1"], + ["1.2.3-1", " v1.2.3-1"], + ["1.2.3-1", " =1.2.3-1"], + ["1.2.3-1", " v 1.2.3-1"], + ["1.2.3-1", " = 1.2.3-1"], + ["1.2.3-beta", "v1.2.3-beta"], + ["1.2.3-beta", "=1.2.3-beta"], + ["1.2.3-beta", "v 1.2.3-beta"], + ["1.2.3-beta", "= 1.2.3-beta"], + ["1.2.3-beta", " v1.2.3-beta"], + ["1.2.3-beta", " =1.2.3-beta"], + ["1.2.3-beta", " v 1.2.3-beta"], + ["1.2.3-beta", " = 1.2.3-beta"], + ["1.2.3-beta+build", " = 1.2.3-beta+otherbuild"], + ["1.2.3+build", " = 1.2.3+otherbuild"], + ["1.2.3-beta+build", "1.2.3-beta+otherbuild"], + ["1.2.3+build", "1.2.3+otherbuild"], + [" v1.2.3+build", "1.2.3+otherbuild"], + ]; + + for (const [left, right] of tests) { + expect(order(left, right)).toBe(0); + expect(order(right, left)).toBe(0); + } + }); +}); + +describe("Bun.semver.satisfies()", () => { + test("expected errors", () => { + expect(satisfies).toBeInstanceOf(Function); + expect(() => { + // @ts-expect-error + satisfies(); + }).toThrow("Expected two arguments"); + expect(() => { + // @ts-expect-error + satisfies("1.2.3"); + }).toThrow("Expected two arguments"); + // @ts-expect-error + expect(satisfies("1.2.3", "1.2.3", "blah")).toBeTrue(); + expect(() => { + satisfies(Symbol.for("~1.2.3"), "1.2.3"); + }).toThrow("Cannot convert a symbol to a string"); + expect(() => { + satisfies(Symbol.for("~1.2.3"), Symbol.for("1.2.3")); + }).toThrow("Cannot convert a symbol to a string"); + expect(() => { + satisfies("~1.2.3", Symbol.for("1.2.3")); + }).toThrow("Cannot convert a symbol to a string"); + }); + + test("failures does not cause weird memory issues", () => { + for (let i = 0; i < 1e5; i++) { + if (!satisfies("1.2.3", "1.2.3")) { + expect().fail("Expected true"); + } + + if (satisfies("^2.2.3||lol||!!#4_", "1.2.3")) { + expect().fail("Expected false"); + } + + if (satisfies("^1.2.3||lol||!!#4_", "+!+!+!_)31231.2.3")) { + expect().fail("Expected false"); + } + + if (!satisfies("1.2.3", "^1.2.3")) { + expect().fail("Expected true"); + } + + if (satisfies("^1.2.3", "1.2.3")) { + expect().fail("Expected false"); + } + } + Bun.gc(true); + }); + + test("exact versions", () => { + testSatisfiesExact("1.2.3", "1.2.3", true); + testSatisfiesExact("4", "4", false); + testSatisfiesExact("4.0.0", "4.0.0", true); + testSatisfiesExact("4.0", "4.0", false); + testSatisfiesExact("5.0.0-beta.1", "5.0.0-beta.1", true); + testSatisfiesExact("5.0.0-beta.1", "5.0.0-beta.2", false); + testSatisfiesExact("5.0.0-beta.1", "5.0.0-beta.0", false); + testSatisfiesExact("5.0.0-beta.1", "5.0.0-beta", false); + testSatisfiesExact("5.0.0-beta.1", "5.0.0", false); + }); + + test("ranges", () => { + testSatisfies("~1.2.3", "1.2.3", true); + testSatisfies("~1.2", "1.2.0", true); + testSatisfies("~1", "1.0.0", true); + testSatisfies("~1", "1.2.0", true); + testSatisfies("~1", "1.2.999", true); + testSatisfies("~0.2.3", "0.2.3", true); + testSatisfies("~0.2", "0.2.0", true); + testSatisfies("~0.2", "0.2.1", true); + testSatisfies("~0 ", "0.0.0", true); + + testSatisfies("~1.2.3", "1.3.0", false); + testSatisfies("~1.2", "1.3.0", false); + testSatisfies("~1", "2.0.0", false); + testSatisfies("~0.2.3", "0.3.0", false); + testSatisfies("~0.2.3", "1.0.0", false); + testSatisfies("~0 ", "1.0.0", false); + testSatisfies("~0.2", "0.1.0", false); + testSatisfies("~0.2", "0.3.0", false); + + testSatisfies("~3.0.5", "3.3.0", false); + + testSatisfies("^1.1.4", "1.1.4", true); + + testSatisfies(">=3", "3.5.0", true); + testSatisfies(">=3", "2.999.999", false); + testSatisfies(">=3", "3.5.1", true); + testSatisfies(">=3.x.x", "3.x.x", false); + + testSatisfies("<6 >= 5", "5.0.0", true); + testSatisfies("<6 >= 5", "4.0.0", false); + testSatisfies("<6 >= 5", "6.0.0", false); + testSatisfies("<6 >= 5", "6.0.1", false); + + testSatisfies(">2", "3", false); + testSatisfies(">2", "2.1", false); + testSatisfies(">2", "2", false); + testSatisfies(">2", "1.0", false); + testSatisfies(">1.3", "1.3.1", false); + testSatisfies(">1.3", "2.0.0", true); + testSatisfies(">2.1.0", "2.2.0", true); + testSatisfies("<=2.2.99999", "2.2.0", true); + testSatisfies(">=2.1.99999", "2.2.0", true); + testSatisfies("<2.2.99999", "2.2.0", true); + testSatisfies(">2.1.99999", "2.2.0", true); + testSatisfies(">1.0.0", "2.0.0", true); + testSatisfies("1.0.0", "1.0.0", true); + testSatisfies("1.0.0", "2.0.0", false); + + testSatisfies("1.0.0 || 2.0.0", "1.0.0", true); + testSatisfies("2.0.0 || 1.0.0", "1.0.0", true); + testSatisfies("1.0.0 || 2.0.0", "2.0.0", true); + testSatisfies("2.0.0 || 1.0.0", "2.0.0", true); + testSatisfies("2.0.0 || >1.0.0", "2.0.0", true); + + testSatisfies(">1.0.0 <2.0.0 <2.0.1 >1.0.1", "1.0.2", true); + + testSatisfies("2.x", "2.0.0", true); + testSatisfies("2.x", "2.1.0", true); + testSatisfies("2.x", "2.2.0", true); + testSatisfies("2.x", "2.3.0", true); + testSatisfies("2.x", "2.1.1", true); + testSatisfies("2.x", "2.2.2", true); + testSatisfies("2.x", "2.3.3", true); + + testSatisfies("<2.0.1 >1.0.0", "2.0.0", true); + testSatisfies("<=2.0.1 >=1.0.0", "2.0.0", true); + + testSatisfies("^2", "2.0.0", true); + testSatisfies("^2", "2.9.9", true); + testSatisfies("~2", "2.0.0", true); + testSatisfies("~2", "2.1.0", true); + testSatisfies("~2.2", "2.2.1", true); + + testSatisfies("2.1.0 || > 2.2 || >3", "2.1.0", true); + testSatisfies(" > 2.2 || >3 || 2.1.0", "2.1.0", true); + testSatisfies(" > 2.2 || 2.1.0 || >3", "2.1.0", true); + testSatisfies("> 2.2 || 2.1.0 || >3", "2.3.0", true); + testSatisfies("> 2.2 || 2.1.0 || >3", "2.2.1", false); + testSatisfies("> 2.2 || 2.1.0 || >3", "2.2.0", false); + testSatisfies("> 2.2 || 2.1.0 || >3", "2.3.0", true); + testSatisfies("> 2.2 || 2.1.0 || >3", "3.0.1", true); + testSatisfies("~2", "2.0.0", true); + testSatisfies("~2", "2.1.0", true); + + testSatisfies("1.2.0 - 1.3.0", "1.2.2", true); + testSatisfies("1.2 - 1.3", "1.2.2", true); + testSatisfies("1 - 1.3", "1.2.2", true); + testSatisfies("1 - 1.3", "1.3.0", true); + testSatisfies("1.2 - 1.3", "1.3.1", true); + testSatisfies("1.2 - 1.3", "1.4.0", false); + testSatisfies("1 - 1.3", "1.3.1", true); + + testSatisfies("1.2 - 1.3 || 5.0", "6.4.0", false); + testSatisfies("1.2 - 1.3 || 5.0", "1.2.1", true); + testSatisfies("5.0 || 1.2 - 1.3", "1.2.1", true); + testSatisfies("1.2 - 1.3 || 5.0", "5.0", false); + testSatisfies("5.0 || 1.2 - 1.3", "5.0", false); + testSatisfies("1.2 - 1.3 || 5.0", "5.0.2", true); + testSatisfies("5.0 || 1.2 - 1.3", "5.0.2", true); + testSatisfies("1.2 - 1.3 || 5.0", "5.0.2", true); + testSatisfies("5.0 || 1.2 - 1.3", "5.0.2", true); + testSatisfies("5.0 || 1.2 - 1.3 || >8", "9.0.2", true); + + const notPassing = [ + "0.1.0", + "0.10.0", + "0.2.0", + "0.2.1", + "0.2.2", + "0.3.0", + "0.3.1", + "0.3.2", + "0.4.0", + "0.4.1", + "0.4.2", + "0.5.0", + "0.5.0-rc.1", + "0.5.1", + "0.5.2", + "0.6.0", + "0.6.1", + "0.7.0", + "0.8.0", + "0.8.1", + "0.8.2", + "0.9.0", + "0.9.1", + "0.9.2", + "1.0.0", + "1.0.1", + "1.0.2", + "1.1.0", + "1.1.1", + "1.2.0", + "1.2.1", + "1.3.0", + "1.3.1", + "2.2.0", + "2.2.1", + "2.3.0", + "1.0.0-rc.1", + "1.0.0-rc.2", + "1.0.0-rc.3", + ]; + + for (const item of notPassing) { + testSatisfies("^2 <2.2 || > 2.3", item, false); + testSatisfies("> 2.3 || ^2 <2.2", item, false); + } + + const passing = [ + "2.4.0", + "2.4.1", + "3.0.0", + "3.0.1", + "3.1.0", + "3.2.0", + "3.3.0", + "3.3.1", + "3.4.0", + "3.5.0", + "3.6.0", + "3.7.0", + "2.4.2", + "3.8.0", + "3.9.0", + "3.9.1", + "3.9.2", + "3.9.3", + "3.10.0", + "3.10.1", + "4.0.0", + "4.0.1", + "4.1.0", + "4.2.0", + "4.2.1", + "4.3.0", + "4.4.0", + "4.5.0", + "4.5.1", + "4.6.0", + "4.6.1", + "4.7.0", + "4.8.0", + "4.8.1", + "4.8.2", + "4.9.0", + "4.10.0", + "4.11.0", + "4.11.1", + "4.11.2", + "4.12.0", + "4.13.0", + "4.13.1", + "4.14.0", + "4.14.1", + "4.14.2", + "4.15.0", + "4.16.0", + "4.16.1", + "4.16.2", + "4.16.3", + "4.16.4", + "4.16.5", + "4.16.6", + "4.17.0", + "4.17.1", + "4.17.2", + "4.17.3", + "4.17.4", + "4.17.5", + "4.17.9", + "4.17.10", + "4.17.11", + "2.0.0", + "2.1.0", + ]; + + for (const item of passing) { + testSatisfies("^2 <2.2 || > 2.3", item, true); + testSatisfies("> 2.3 || ^2 <2.2", item, true); + } + }); + + test("range includes", () => { + // https://github.com/npm/node-semver/blob/14d263faa156e408a033b9b12a2f87735c2df42c/test/fixtures/range-include.js#L3 + var tests = [ + ["1.0.0 - 2.0.0", "1.2.3"], + ["^1.2.3+build", "1.2.3"], + ["^1.2.3+build", "1.3.0"], + ["1.2.3-pre+asdf - 2.4.3-pre+asdf", "1.2.3"], + ["1.2.3pre+asdf - 2.4.3-pre+asdf", "1.2.3"], + ["1.2.3-pre+asdf - 2.4.3pre+asdf", "1.2.3"], + ["1.2.3pre+asdf - 2.4.3pre+asdf", "1.2.3"], + ["1.2.3-pre+asdf - 2.4.3-pre+asdf", "1.2.3-pre.2"], + ["1.2.3-pre+asdf - 2.4.3-pre+asdf", "2.4.3-alpha"], + ["1.2.3+asdf - 2.4.3+asdf", "1.2.3"], + ["1.0.0", "1.0.0"], + [">=*", "0.2.4"], + ["", "1.0.0"], + ["*", "1.2.3"], + ["*", "v1.2.3"], + [">=1.0.0", "1.0.0"], + [">=1.0.0", "1.0.1"], + [">=1.0.0", "1.1.0"], + [">1.0.0", "1.0.1"], + [">1.0.0", "1.1.0"], + ["<=2.0.0", "2.0.0"], + ["<=2.0.0", "1.9999.9999"], + ["<=2.0.0", "0.2.9"], + ["<2.0.0", "1.9999.9999"], + ["<2.0.0", "0.2.9"], + [">= 1.0.0", "1.0.0"], + [">= 1.0.0", "1.0.1"], + [">= 1.0.0", "1.1.0"], + ["> 1.0.0", "1.0.1"], + ["> 1.0.0", "1.1.0"], + ["<= 2.0.0", "2.0.0"], + ["<= 2.0.0", "1.9999.9999"], + ["<= 2.0.0", "0.2.9"], + ["< 2.0.0", "1.9999.9999"], + ["<\t2.0.0", "0.2.9"], + [">=0.1.97", "v0.1.97", true], + [">=0.1.97", "0.1.97"], + ["0.1.20 || 1.2.4", "1.2.4"], + [">=0.2.3 || <0.0.1", "0.0.0"], + [">=0.2.3 || <0.0.1", "0.2.3"], + [">=0.2.3 || <0.0.1", "0.2.4"], + ["||", "1.3.4"], + ["2.x.x", "2.1.3"], + ["1.2.x", "1.2.3"], + ["1.2.x || 2.x", "2.1.3"], + ["1.2.x || 2.x", "1.2.3"], + ["x", "1.2.3"], + ["2.*.*", "2.1.3"], + ["1.2.*", "1.2.3"], + ["1.2.* || 2.*", "2.1.3"], + ["1.2.* || 2.*", "1.2.3"], + ["*", "1.2.3"], + ["2", "2.1.2"], + ["2.3", "2.3.1"], + ["~0.0.1", "0.0.1"], + ["~0.0.1", "0.0.2"], + ["~x", "0.0.9"], // >=2.4.0 <2.5.0 + ["~2", "2.0.9"], // >=2.4.0 <2.5.0 + ["~2.4", "2.4.0"], // >=2.4.0 <2.5.0 + ["~2.4", "2.4.5"], + ["~>3.2.1", "3.2.2"], // >=3.2.1 <3.3.0, + ["~1", "1.2.3"], // >=1.0.0 <2.0.0 + ["~>1", "1.2.3"], + ["~> 1", "1.2.3"], + ["~1.0", "1.0.2"], // >=1.0.0 <1.1.0, + ["~ 1.0", "1.0.2"], + ["~ 1.0.3", "1.0.12"], + ["~ 1.0.3alpha", "1.0.12"], + [">=1", "1.0.0"], + [">= 1", "1.0.0"], + ["<1.2", "1.1.1"], + ["< 1.2", "1.1.1"], + ["~v0.5.4-pre", "0.5.5"], + ["~v0.5.4-pre", "0.5.4"], + ["=0.7.x", "0.7.2"], + ["<=0.7.x", "0.7.2"], + [">=0.7.x", "0.7.2"], + ["<=0.7.x", "0.6.2"], + ["~1.2.1 >=1.2.3", "1.2.3"], + ["~1.2.1 =1.2.3", "1.2.3"], + ["~1.2.1 1.2.3", "1.2.3"], + ["~1.2.1 >=1.2.3 1.2.3", "1.2.3"], + ["~1.2.1 1.2.3 >=1.2.3", "1.2.3"], + [">=1.2.1 1.2.3", "1.2.3"], + ["1.2.3 >=1.2.1", "1.2.3"], + [">=1.2.3 >=1.2.1", "1.2.3"], + [">=1.2.1 >=1.2.3", "1.2.3"], + [">=1.2", "1.2.8"], + ["^1.2.3", "1.8.1"], + ["^0.1.2", "0.1.2"], + ["^0.1", "0.1.2"], + ["^0.0.1", "0.0.1"], + ["^1.2", "1.4.2"], + ["^1.2 ^1", "1.4.2"], + ["^1.2.3-alpha", "1.2.3-pre"], + ["^1.2.0-alpha", "1.2.0-pre"], + ["^0.0.1-alpha", "0.0.1-beta"], + ["^0.0.1-alpha", "0.0.1"], + ["^0.1.1-alpha", "0.1.1-beta"], + ["^x", "1.2.3"], + ["x - 1.0.0", "0.9.7"], + ["x - 1.x", "0.9.7"], + ["1.0.0 - x", "1.9.7"], + ["1.0.0 - x", "1.0.7"], + ["1.0.0 - 1.x", "1.0.7"], + ["1.0.0 - 1.0.x", "1.0.7"], + ["1.x - x", "1.9.7"], + ["<=7.x", "7.9.9"], + + // ["2.x", "2.0.0-pre.0", { includePrerelease: true }], + // ["2.x", "2.1.0-pre.0", { includePrerelease: true }], + // ["1.1.x", "1.1.0-a", { includePrerelease: true }], + // ["1.1.x", "1.1.1-a", { includePrerelease: true }], + // ["*", "1.0.0-rc1", { includePrerelease: true }], + // ["^1.0.0-0", "1.0.1-rc1", { includePrerelease: true }], + // ["^1.0.0-rc2", "1.0.1-rc1", { includePrerelease: true }], + // ["^1.0.0", "1.0.1-rc1", { includePrerelease: true }], + // ["^1.0.0", "1.1.0-rc1", { includePrerelease: true }], + // ["1 - 2", "2.0.0-pre", { includePrerelease: true }], + // ["1 - 2", "1.0.0-pre", { includePrerelease: true }], + // ["1.0 - 2", "1.0.0-pre", { includePrerelease: true }], + + // ["=0.7.x", "0.7.0-asdf", { includePrerelease: true }], + // [">=0.7.x", "0.7.0-asdf", { includePrerelease: true }], + // ["<=0.7.x", "0.7.0-asdf", { includePrerelease: true }], + + // [">=1.0.0 <=1.1.0", "1.1.0-pre", { includePrerelease: true }], + ]; + + for (const [range, version] of tests) { + expect(satisfies(version, range)).toBeTrue(); + } + }); + + test("range excludes", () => { + // https://github.com/npm/node-semver/blob/14d263faa156e408a033b9b12a2f87735c2df42c/test/fixtures/range-exclude.js#L3 + const tests = [ + ["1.0.0 - 2.0.0", "2.2.3"], + ["1.2.3+asdf - 2.4.3+asdf", "1.2.3-pre.2"], + ["1.2.3+asdf - 2.4.3+asdf", "2.4.3-alpha"], + ["^1.2.3+build", "2.0.0"], + ["^1.2.3+build", "1.2.0"], + ["^1.2.3", "1.2.3-pre"], + ["^1.2", "1.2.0-pre"], + [">1.2", "1.3.0-beta"], + ["<=1.2.3", "1.2.3-beta"], + ["^1.2.3", "1.2.3-beta"], + ["=0.7.x", "0.7.0-asdf"], + [">=0.7.x", "0.7.0-asdf"], + ["<=0.7.x", "0.7.0-asdf"], + ["1", "1.0.0beta"], + ["<1", "1.0.0beta"], + ["< 1", "1.0.0beta"], + ["1.0.0", "1.0.1"], + [">=1.0.0", "0.0.0"], + [">=1.0.0", "0.0.1"], + [">=1.0.0", "0.1.0"], + [">1.0.0", "0.0.1"], + [">1.0.0", "0.1.0"], + ["<=2.0.0", "3.0.0"], + ["<=2.0.0", "2.9999.9999"], + ["<=2.0.0", "2.2.9"], + ["<2.0.0", "2.9999.9999"], + ["<2.0.0", "2.2.9"], + [">=0.1.97", "v0.1.93"], + [">=0.1.97", "0.1.93"], + ["0.1.20 || 1.2.4", "1.2.3"], + [">=0.2.3 || <0.0.1", "0.0.3"], + [">=0.2.3 || <0.0.1", "0.2.2"], + ["2.x.x", "1.1.3"], + ["2.x.x", "3.1.3"], + ["1.2.x", "1.3.3"], + ["1.2.x || 2.x", "3.1.3"], + ["1.2.x || 2.x", "1.1.3"], + ["2.*.*", "1.1.3"], + ["2.*.*", "3.1.3"], + ["1.2.*", "1.3.3"], + ["1.2.* || 2.*", "3.1.3"], + ["1.2.* || 2.*", "1.1.3"], + ["2", "1.1.2"], + ["2.3", "2.4.1"], + ["~0.0.1", "0.1.0-alpha"], + ["~0.0.1", "0.1.0"], + ["~2.4", "2.5.0"], // >=2.4.0 <2.5.0 + ["~2.4", "2.3.9"], + ["~>3.2.1", "3.3.2"], // >=3.2.1 <3.3.0 + ["~>3.2.1", "3.2.0"], // >=3.2.1 <3.3.0 + ["~1", "0.2.3"], // >=1.0.0 <2.0.0 + ["~>1", "2.2.3"], + ["~1.0", "1.1.0"], // >=1.0.0 <1.1.0 + ["<1", "1.0.0"], + [">=1.2", "1.1.1"], + ["1", "2.0.0beta"], + ["~v0.5.4-beta", "0.5.4-alpha"], + ["=0.7.x", "0.8.2"], + [">=0.7.x", "0.6.2"], + ["<0.7.x", "0.7.2"], + ["<1.2.3", "1.2.3-beta"], + ["=1.2.3", "1.2.3-beta"], + [">1.2", "1.2.8"], + ["^0.0.1", "0.0.2-alpha"], + ["^0.0.1", "0.0.2"], + ["^1.2.3", "2.0.0-alpha"], + ["^1.2.3", "1.2.2"], + ["^1.2", "1.1.9"], + ["*", "v1.2.3-foo"], + + // invalid versions never satisfy, but shouldn't throw + ["*", "not a version"], + [">=2", "glorp"], + [">=2", false], + + // ["2.x", "3.0.0-pre.0", { includePrerelease: true }], + // ["^1.0.0", "1.0.0-rc1", { includePrerelease: true }], + // ["^1.0.0", "2.0.0-rc1", { includePrerelease: true }], + // ["^1.2.3-rc2", "2.0.0", { includePrerelease: true }], + ["^1.0.0", "2.0.0-rc1"], + + // ["1 - 2", "3.0.0-pre", { includePrerelease: true }], + ["1 - 2", "2.0.0-pre"], + ["1 - 2", "1.0.0-pre"], + ["1.0 - 2", "1.0.0-pre"], + + ["1.1.x", "1.0.0-a"], + ["1.1.x", "1.1.0-a"], + ["1.1.x", "1.2.0-a"], + // ["1.1.x", "1.2.0-a", { includePrerelease: true }], + // ["1.1.x", "1.0.0-a", { includePrerelease: true }], + ["1.x", "1.0.0-a"], + ["1.x", "1.1.0-a"], + ["1.x", "1.2.0-a"], + // ["1.x", "0.0.0-a", { includePrerelease: true }], + // ["1.x", "2.0.0-a", { includePrerelease: true }], + + [">=1.0.0 <1.1.0", "1.1.0"], + // [">=1.0.0 <1.1.0", "1.1.0", { includePrerelease: true }], + [">=1.0.0 <1.1.0", "1.1.0-pre"], + [">=1.0.0 <1.1.0-pre", "1.1.0-pre"], + + ["== 1.0.0 || foo", "2.0.0"], + ]; + + for (const [range, version] of tests) { + expect(satisfies(version, range)).toBeFalse(); + } + }); +});