aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Alex Lam S.L <alexlamsl@gmail.com> 2023-01-19 06:11:52 +0200
committerGravatar GitHub <noreply@github.com> 2023-01-18 20:11:52 -0800
commitc0ec61cf16633492befd8f560ff6970db01c2a1d (patch)
tree96ca85691de8171072a8cefe81f8a90986967a03
parentd4e323b997bfda0bb140cac4fb0a621a35953fb7 (diff)
downloadbun-c0ec61cf16633492befd8f560ff6970db01c2a1d.tar.gz
bun-c0ec61cf16633492befd8f560ff6970db01c2a1d.tar.zst
bun-c0ec61cf16633492befd8f560ff6970db01c2a1d.zip
support npm dependency aliasing (#1837)
* support npm dependency aliasing * fix variable name
-rw-r--r--src/bun.js/module_loader.zig4
-rw-r--r--src/install/dependency.zig84
-rw-r--r--src/install/install.zig210
-rw-r--r--src/install/lockfile.zig46
-rw-r--r--src/install/resolvers/folder_resolver.zig2
-rw-r--r--src/resolver/package_json.zig12
-rw-r--r--src/resolver/resolver.zig9
-rw-r--r--test/bun.js/install/bun-install.test.ts154
8 files changed, 265 insertions, 256 deletions
diff --git a/src/bun.js/module_loader.zig b/src/bun.js/module_loader.zig
index 91081786b..01ae2771a 100644
--- a/src/bun.js/module_loader.zig
+++ b/src/bun.js/module_loader.zig
@@ -621,9 +621,9 @@ pub const ModuleLoader = struct {
.{ result.name, result.url },
),
error.DistTagNotFound, error.NoMatchingVersion => brk: {
- const prefix: []const u8 = if (result.err == error.NoMatchingVersion and result.version.tag == .npm and result.version.value.npm.isExact())
+ const prefix: []const u8 = if (result.err == error.NoMatchingVersion and result.version.tag == .npm and result.version.value.npm.version.isExact())
"Version not found"
- else if (result.version.tag == .npm and !result.version.value.npm.isExact())
+ else if (result.version.tag == .npm and !result.version.value.npm.version.isExact())
"No matching version found"
else
"No match found";
diff --git a/src/install/dependency.zig b/src/install/dependency.zig
index af28c99ba..44ca91d51 100644
--- a/src/install/dependency.zig
+++ b/src/install/dependency.zig
@@ -88,12 +88,14 @@ pub fn cloneWithDifferentBuffers(this: Dependency, name_buf: []const u8, version
const out_slice = builder.lockfile.buffers.string_bytes.items;
const new_literal = builder.append(String, this.version.literal.slice(version_buf));
const sliced = new_literal.sliced(out_slice);
+ const new_name = builder.append(String, this.name.slice(name_buf));
return Dependency{
.name_hash = this.name_hash,
- .name = builder.append(String, this.name.slice(name_buf)),
+ .name = new_name,
.version = Dependency.parseWithTag(
builder.lockfile.allocator,
+ new_name,
new_literal.slice(out_slice),
this.version.tag,
&sliced,
@@ -120,13 +122,14 @@ pub fn toDependency(
this: External,
ctx: Context,
) Dependency {
+ const name = String{
+ .bytes = this[0..8].*,
+ };
return Dependency{
- .name = String{
- .bytes = this[0..8].*,
- },
+ .name = name,
.name_hash = @bitCast(u64, this[8..16].*),
.behavior = @intToEnum(Dependency.Behavior, this[16]),
- .version = Dependency.Version.toVersion(this[17..this.len].*, ctx),
+ .version = Dependency.Version.toVersion(name, this[17..this.len].*, ctx),
};
}
@@ -147,7 +150,7 @@ pub const Version = struct {
pub fn deinit(this: *Version) void {
switch (this.tag) {
.npm => {
- this.value.npm.deinit();
+ this.value.npm.version.deinit();
},
else => {},
}
@@ -195,6 +198,7 @@ pub const Version = struct {
pub const External = [9]u8;
pub fn toVersion(
+ alias: String,
bytes: Version.External,
ctx: Dependency.Context,
) Dependency.Version {
@@ -203,6 +207,7 @@ pub const Version = struct {
const sliced = &slice.sliced(ctx.buffer);
return Dependency.parseWithTag(
ctx.allocator,
+ alias,
sliced.slice,
tag,
sliced,
@@ -231,7 +236,7 @@ pub const Version = struct {
// 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),
+ lhs.value.npm.eql(rhs.value.npm, lhs_buf, rhs_buf),
.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),
@@ -443,10 +448,19 @@ pub const Version = struct {
}
};
+ const NpmInfo = struct {
+ name: String,
+ version: Semver.Query.Group,
+
+ fn eql(this: NpmInfo, that: NpmInfo, this_buf: []const u8, that_buf: []const u8) bool {
+ return this.name.eql(that.name, this_buf, that_buf) and this.version.eql(that.version);
+ }
+ };
+
pub const Value = union {
uninitialized: void,
- npm: Semver.Query.Group,
+ npm: NpmInfo,
dist_tag: String,
tarball: URI,
folder: String,
@@ -481,40 +495,29 @@ pub fn eqlResolved(a: Dependency, b: Dependency) bool {
pub inline fn parse(
allocator: std.mem.Allocator,
+ alias: String,
dependency: string,
sliced: *const SlicedString,
log: ?*logger.Log,
) ?Version {
- return parseWithOptionalTag(allocator, dependency, null, sliced, log);
+ return parseWithOptionalTag(allocator, alias, dependency, null, sliced, log);
}
pub fn parseWithOptionalTag(
allocator: std.mem.Allocator,
+ alias: String,
dependency_: string,
tag_or_null: ?Dependency.Version.Tag,
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 = tag_or_null orelse Version.Tag.infer(dependency);
-
- if (tag == .npm and strings.hasPrefixComptime(dependency, "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,
+ alias,
dependency,
- tag,
+ tag_or_null orelse Version.Tag.infer(dependency),
sliced,
log,
);
@@ -522,6 +525,7 @@ pub fn parseWithOptionalTag(
pub fn parseWithTag(
allocator: std.mem.Allocator,
+ alias: String,
dependency: string,
tag: Dependency.Version.Tag,
sliced: *const SlicedString,
@@ -529,10 +533,31 @@ pub fn parseWithTag(
) ?Version {
switch (tag) {
.npm => {
+ var input = dependency;
+ const name = if (strings.hasPrefixComptime(input, "npm:")) sliced.sub(brk: {
+ var str = input["npm:".len..];
+ var i: usize = 0;
+ while (i < str.len) : (i += 1) {
+ if (str[i] == '@') {
+ input = str[i + 1 ..];
+ break :brk str[0..i];
+ }
+ }
+ input = str[i..];
+ break :brk str[0..i];
+ }).value() else alias;
+
+ // 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 (input.len > 1 and input[0] == 'v') {
+ input = input[1..];
+ }
+
const version = Semver.Query.parse(
allocator,
- dependency,
- sliced.sub(dependency),
+ input,
+ sliced.sub(input),
) catch |err| {
if (log_) |log| log.addErrorFmt(null, logger.Loc.Empty, allocator, "{s} parsing dependency \"{s}\"", .{ @errorName(err), dependency }) catch unreachable;
return null;
@@ -540,7 +565,12 @@ pub fn parseWithTag(
return Version{
.literal = sliced.value(),
- .value = .{ .npm = version },
+ .value = .{
+ .npm = .{
+ .name = name,
+ .version = version,
+ },
+ },
.tag = .npm,
};
},
diff --git a/src/install/install.zig b/src/install/install.zig
index 6a402cfbc..86e0ca532 100644
--- a/src/install/install.zig
+++ b/src/install/install.zig
@@ -635,89 +635,6 @@ const PackageInstall = struct {
package_version: string,
file_count: u32 = 0,
- threadlocal var package_json_checker: json_parser.PackageJSONVersionChecker = undefined;
-
- pub const Context = struct {
- metas: []const Lockfile.Package.Meta,
- names: []const String,
- resolutions: []const Resolution,
- string_buf: []const u8,
- channel: PackageInstall.Task.Channel = undefined,
- skip_verify: bool = false,
- progress: *Progress = undefined,
- cache_dir: std.fs.IterableDir = undefined,
- allocator: std.mem.Allocator,
- };
-
- pub const Task = struct {
- task: ThreadPool.Task = .{ .callback = &callback },
- result: Result = Result{ .pending = void{} },
- package_install: PackageInstall = undefined,
- package_id: PackageID,
- ctx: *PackageInstall.Context,
- destination_dir: std.fs.IterableDir,
-
- pub const Channel = sync.Channel(*PackageInstall.Task, .{ .Static = 1024 });
-
- pub fn callback(task: *ThreadPool.Task) void {
- Output.Source.configureThread();
- defer Output.flush();
-
- var this: *PackageInstall.Task = @fieldParentPtr(PackageInstall.Task, "task", task);
- var ctx = this.ctx;
-
- var destination_dir_subpath_buf: [bun.MAX_PATH_BYTES]u8 = undefined;
- var cache_dir_subpath_buf: [bun.MAX_PATH_BYTES]u8 = undefined;
- const name = ctx.names[this.package_id].slice(ctx.string_buf);
- const resolution = ctx.resolutions[this.package_id];
- std.mem.copy(u8, &destination_dir_subpath_buf, name);
- destination_dir_subpath_buf[name.len] = 0;
- var destination_dir_subpath: [:0]u8 = destination_dir_subpath_buf[0..name.len :0];
- var resolution_buf: [512]u8 = undefined;
- var resolution_label = std.fmt.bufPrint(&resolution_buf, "{}", .{resolution.fmt(ctx.string_buf)}) catch unreachable;
-
- this.package_install = PackageInstall{
- .cache_dir = undefined,
- .cache_dir_subpath = undefined,
- .progress = ctx.progress,
-
- .destination_dir = this.destination_dir,
- .destination_dir_subpath = destination_dir_subpath,
- .destination_dir_subpath_buf = &destination_dir_subpath_buf,
- .allocator = ctx.allocator,
- .package_name = name,
- .package_version = resolution_label,
- };
-
- switch (resolution.tag) {
- .npm => {
- this.package_install.cache_dir_subpath = this.manager.cachedNPMPackageFolderName(name, resolution.value.npm);
- this.package_install.cache_dir = this.manager.getCacheDirectory();
- },
- .folder => {
- var folder_buf = &cache_dir_subpath_buf;
- const folder = resolution.value.folder.slice(ctx.string_buf);
- std.mem.copy(u8, folder_buf, "../" ++ std.fs.path.sep_str);
- std.mem.copy(u8, folder_buf["../".len..], folder);
- folder_buf["../".len + folder.len] = 0;
- this.package_install.cache_dir_subpath = folder_buf[0 .. "../".len + folder.len :0];
- this.package_install.cache_dir = std.fs.cwd();
- },
- else => return,
- }
-
- const needs_install = ctx.skip_verify_installed_version_number or !this.package_install.verify();
-
- if (needs_install) {
- this.result = this.package_install.install(ctx.skip_verify_installed_version_number);
- } else {
- this.result = .{ .skip = .{} };
- }
-
- ctx.channel.writeItem(this) catch unreachable;
- }
- };
-
pub const Summary = struct {
fail: u32 = 0,
success: u32 = 0,
@@ -832,7 +749,7 @@ const PackageInstall = struct {
initializeStore();
- package_json_checker = json_parser.PackageJSONVersionChecker.init(allocator, &source, &log) catch return false;
+ var package_json_checker = json_parser.PackageJSONVersionChecker.init(allocator, &source, &log) catch return false;
_ = package_json_checker.parseExpr() catch return false;
if (!package_json_checker.has_found_name or !package_json_checker.has_found_version or log.errors > 0) return false;
@@ -1458,6 +1375,7 @@ pub const PackageManager = struct {
resolve_tasks: TaskChannel,
timestamp_for_manifest_cache_control: u32 = 0,
extracted_count: u32 = 0,
+ alias_map: std.ArrayHashMapUnmanaged(PackageID, String, ArrayIdentityContext, false) = .{},
default_features: Features = Features{},
summary: Lockfile.Package.Diff.Summary = Lockfile.Package.Diff.Summary{},
env: *DotEnv.Loader,
@@ -2060,7 +1978,7 @@ pub const PackageManager = struct {
Semver.Version.sortGt,
);
for (installed_versions.items) |installed_version| {
- if (version.value.npm.satisfies(installed_version)) {
+ if (version.value.npm.version.satisfies(installed_version)) {
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}", .{std.mem.span(@errorName(err))});
@@ -2069,7 +1987,10 @@ pub const PackageManager = struct {
const dependency = Dependency.Version{
.tag = .npm,
.value = .{
- .npm = Semver.Query.Group.from(installed_version),
+ .npm = .{
+ .name = String.init(package_name, package_name),
+ .version = Semver.Query.Group.from(installed_version),
+ },
},
};
switch (FolderResolution.getOrPut(.{ .cache_folder = npm_package_path }, dependency, ".", this)) {
@@ -2102,8 +2023,9 @@ pub const PackageManager = struct {
network_task: ?*NetworkTask = null,
};
- pub fn getOrPutResolvedPackageWithFindResult(
+ fn getOrPutResolvedPackageWithFindResult(
this: *PackageManager,
+ alias: String,
name_hash: PackageNameHash,
name: String,
version: Dependency.Version,
@@ -2135,8 +2057,8 @@ pub const PackageManager = struct {
};
}
- var package =
- try Lockfile.Package.fromNPM(
+ // appendPackage sets the PackageID on the package
+ const package = try this.lockfile.appendPackage(try Lockfile.Package.fromNPM(
this.allocator,
this.lockfile,
this.log,
@@ -2145,10 +2067,9 @@ pub const PackageManager = struct {
find_result.package,
manifest.string_buf,
Features.npm,
- );
+ ));
- // appendPackage sets the PackageID on the package
- package = try this.lockfile.appendPackage(package);
+ try this.alias_map.put(this.allocator, package.meta.id, alias);
if (!behavior.isEnabled(if (this.isRootDependency(dependency_id))
this.options.local_package_features
@@ -2173,7 +2094,7 @@ pub const PackageManager = struct {
.extract => {
const task_id = Task.Id.forNPMPackage(
Task.Tag.extract,
- name.slice(this.lockfile.buffers.string_bytes.items),
+ this.lockfile.str(name),
package.resolution.value.npm.version,
);
@@ -2234,64 +2155,6 @@ pub const PackageManager = struct {
return network_task;
}
- pub fn fetchVersionsForPackageName(
- this: *PackageManager,
- name: string,
- version: Dependency.Version,
- id: PackageID,
- ) !?Npm.PackageManifest {
- const task_id = Task.Id.forManifest(Task.Tag.package_manifest, name);
- var network_entry = try this.network_dedupe_map.getOrPutContext(this.allocator, task_id, .{});
- var loaded_manifest: ?Npm.PackageManifest = null;
- if (!network_entry.found_existing) {
- if (this.options.enable.manifest_cache) {
- if (this.manifests.get(std.hash.Wyhash.hash(0, name)) orelse (Npm.PackageManifest.Serializer.load(this.allocator, this.cache_directory, name) catch null)) |manifest_| {
- const manifest: Npm.PackageManifest = manifest_;
- loaded_manifest = manifest;
-
- if (this.options.enable.manifest_cache_control and manifest.pkg.public_max_age > this.timestamp_for_manifest_cache_control) {
- try this.manifests.put(this.allocator, @truncate(PackageNameHash, manifest.pkg.name.hash), manifest);
- }
-
- // If it's an exact package version already living in the cache
- // We can skip the network request, even if it's beyond the caching period
- if (version.tag == .npm and version.value.npm.isExact()) {
- if (loaded_manifest.?.findByVersion(version.value.npm.head.head.range.left.version) != null) {
- return manifest;
- }
- }
-
- // Was it recent enough to just load it without the network call?
- if (this.options.enable.manifest_cache_control and manifest.pkg.public_max_age > this.timestamp_for_manifest_cache_control) {
- return manifest;
- }
- }
- }
-
- if (PackageManager.verbose_install) {
- Output.prettyErrorln("Enqueue package manifest for download: {s}", .{name});
- }
-
- var network_task = this.getNetworkTask();
- network_task.* = NetworkTask{
- .callback = undefined,
- .task_id = task_id,
- .allocator = this.allocator,
- .package_manager = this,
- };
- try network_task.forManifest(name, this.allocator, this.scopeForPackageName(name), loaded_manifest);
- this.enqueueNetworkTask(network_task);
- }
-
- var manifest_entry_parse = try this.task_queue.getOrPutContext(this.allocator, task_id, .{});
- if (!manifest_entry_parse.found_existing) {
- manifest_entry_parse.value_ptr.* = TaskCallbackList{};
- }
-
- try manifest_entry_parse.value_ptr.append(this.allocator, TaskCallbackContext{ .request_id = id });
- return null;
- }
-
fn enqueueNetworkTask(this: *PackageManager, task: *NetworkTask) void {
if (this.network_task_fifo.writableLength() == 0) {
this.flushNetworkQueue();
@@ -2319,8 +2182,9 @@ pub const PackageManager = struct {
}
}
- pub fn getOrPutResolvedPackage(
+ fn getOrPutResolvedPackage(
this: *PackageManager,
+ alias: String,
name_hash: PackageNameHash,
name: String,
version: Dependency.Version,
@@ -2339,7 +2203,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)),
- .npm => manifest.findBestVersion(version.value.npm),
+ .npm => manifest.findBestVersion(version.value.npm.version),
else => unreachable,
} orelse return switch (version.tag) {
.npm => error.NoMatchingVersion,
@@ -2349,6 +2213,7 @@ pub const PackageManager = struct {
return try getOrPutResolvedPackageWithFindResult(
this,
+ alias,
name_hash,
name,
version,
@@ -2555,9 +2420,16 @@ pub const PackageManager = struct {
comptime successFn: SuccessFn,
comptime failFn: ?FailFn,
) !void {
- const name = dependency.name;
- const name_hash = dependency.name_hash;
- const version: Dependency.Version = dependency.version;
+ const alias = dependency.name;
+ const name = switch (dependency.version.tag) {
+ .npm => dependency.version.value.npm.name,
+ else => alias,
+ };
+ const name_hash = switch (dependency.version.tag) {
+ .npm => Lockfile.stringHash(this.lockfile.str(name)),
+ else => dependency.name_hash,
+ };
+ const version = dependency.version;
var loaded_manifest: ?Npm.PackageManifest = null;
if (comptime !is_main) {
@@ -2574,6 +2446,7 @@ pub const PackageManager = struct {
.dist_tag, .folder, .npm => {
retry_from_manifests_ptr: while (true) {
var resolve_result_ = this.getOrPutResolvedPackage(
+ alias,
name_hash,
name,
version,
@@ -2693,9 +2566,10 @@ pub const PackageManager = struct {
// If it's an exact package version already living in the cache
// We can skip the network request, even if it's beyond the caching period
- if (dependency.version.tag == .npm and dependency.version.value.npm.isExact()) {
- if (loaded_manifest.?.findByVersion(dependency.version.value.npm.head.head.range.left.version)) |find_result| {
+ if (dependency.version.tag == .npm and dependency.version.value.npm.version.isExact()) {
+ if (loaded_manifest.?.findByVersion(dependency.version.value.npm.version.head.head.range.left.version)) |find_result| {
if (this.getOrPutResolvedPackageWithFindResult(
+ alias,
name_hash,
name,
version,
@@ -2721,7 +2595,7 @@ pub const PackageManager = struct {
}
if (PackageManager.verbose_install) {
- Output.prettyErrorln("Enqueue package manifest for download: {s}", .{this.lockfile.str(name)});
+ Output.prettyErrorln("Enqueue package manifest for download: {s}", .{name_str});
}
var network_task = this.getNetworkTask();
@@ -2732,9 +2606,9 @@ pub const PackageManager = struct {
.allocator = this.allocator,
};
try network_task.forManifest(
- this.lockfile.str(name),
+ name_str,
this.allocator,
- this.scopeForPackageName(this.lockfile.str(name)),
+ this.scopeForPackageName(name_str),
loaded_manifest,
);
this.enqueueNetworkTask(network_task);
@@ -2757,6 +2631,7 @@ pub const PackageManager = struct {
},
.symlink, .workspace => {
const _result = this.getOrPutResolvedPackage(
+ alias,
name_hash,
name,
version,
@@ -5120,7 +4995,7 @@ pub const PackageManager = struct {
request.missing_version = true;
} else {
const sliced = SlicedString.init(request.version_buf, request.version_buf);
- request.version = Dependency.parse(allocator, request.version_buf, &sliced, log) orelse Dependency.Version{};
+ request.version = Dependency.parse(allocator, String.init(request.name, request.name), request.version_buf, &sliced, log) orelse Dependency.Version{};
}
update_requests.append(request) catch break;
@@ -5287,7 +5162,7 @@ pub const PackageManager = struct {
);
}
- pub fn updatePackageJSONAndInstallWithManagerWithUpdates(
+ fn updatePackageJSONAndInstallWithManagerWithUpdates(
ctx: Command.Context,
manager: *PackageManager,
updates: []UpdateRequest,
@@ -5654,11 +5529,12 @@ pub const PackageManager = struct {
name: string,
resolution: Resolution,
) void {
- std.mem.copy(u8, &this.destination_dir_subpath_buf, name);
- this.destination_dir_subpath_buf[name.len] = 0;
- var destination_dir_subpath: [:0]u8 = this.destination_dir_subpath_buf[0..name.len :0];
- var resolution_buf: [512]u8 = undefined;
const buf = this.lockfile.buffers.string_bytes.items;
+ const alias = if (this.manager.alias_map.get(package_id)) |str| str.slice(buf) else name;
+ std.mem.copy(u8, &this.destination_dir_subpath_buf, alias);
+ this.destination_dir_subpath_buf[alias.len] = 0;
+ var destination_dir_subpath: [:0]u8 = this.destination_dir_subpath_buf[0..alias.len :0];
+ var resolution_buf: [512]u8 = undefined;
const extern_string_buf = this.lockfile.buffers.extern_strings.items;
var resolution_label = std.fmt.bufPrint(&resolution_buf, "{}", .{resolution.fmt(buf)}) catch unreachable;
var installer = PackageInstall{
diff --git a/src/install/lockfile.zig b/src/install/lockfile.zig
index 8fc900649..465398fc6 100644
--- a/src/install/lockfile.zig
+++ b/src/install/lockfile.zig
@@ -577,6 +577,7 @@ fn preprocessUpdateRequests(old: *Lockfile, updates: []PackageManager.UpdateRequ
);
dep.version = Dependency.parse(
old.allocator,
+ dep.name,
sliced.slice,
&sliced,
null,
@@ -1523,7 +1524,7 @@ pub fn getPackageID(
switch (version_.tag) {
.npm => {
// is it a peerDependency satisfied by a parent package?
- if (version_.value.npm.satisfies(resolutions[id].value.npm.version)) {
+ if (version_.value.npm.version.satisfies(resolutions[id].value.npm.version)) {
return id;
}
},
@@ -1547,7 +1548,7 @@ pub fn getPackageID(
return id;
}
- if (can_satisfy and version.?.value.npm.satisfies(resolutions[id].value.npm.version)) {
+ if (can_satisfy and version.?.value.npm.version.satisfies(resolutions[id].value.npm.version)) {
return id;
}
}
@@ -1968,9 +1969,7 @@ pub const Package = extern struct {
}
pub fn fromPackageJSON(
- allocator: std.mem.Allocator,
lockfile: *Lockfile,
- log: *logger.Log,
package_json: *PackageJSON,
comptime features: Features,
) !Lockfile.Package {
@@ -2013,39 +2012,12 @@ pub const Package = extern struct {
const package_name: ExternalString = string_builder.append(ExternalString, package_json.name);
package.name_hash = package_name.hash;
package.name = package_name.value;
- var package_version = string_builder.append(String, package_json.version);
- var buf = string_builder.allocatedSlice();
-
- const version: Dependency.Version = brk: {
- if (package_json.version.len > 0) {
- const sliced = package_version.sliced(buf);
- const name = package.name.slice(buf);
- if (Dependency.parse(allocator, name, &sliced, log)) |dep| {
- break :brk dep;
- }
- }
- break :brk Dependency.Version{};
+ package.resolution = .{
+ .tag = .root,
+ .value = .{ .root = {} },
};
- if (version.tag == .npm and version.value.npm.isExact()) {
- package.resolution = Resolution{
- .value = .{
- .npm = .{
- .version = version.value.npm.toVersion(),
- .url = .{},
- },
- },
- .tag = .npm,
- };
- } else {
- package.resolution = Resolution{
- .value = .{
- .root = {},
- },
- .tag = .root,
- };
- }
const total_len = dependencies_list.items.len + total_dependencies_count;
std.debug.assert(dependencies_list.items.len == resolutions_list.items.len);
@@ -2235,6 +2207,7 @@ pub const Package = extern struct {
group.behavior,
.version = Dependency.parse(
allocator,
+ name.value,
sliced.slice,
&sliced,
log,
@@ -2440,6 +2413,7 @@ pub const Package = extern struct {
var dependency_version = Dependency.parseWithOptionalTag(
allocator,
+ external_name.value,
sliced.slice,
tag,
&sliced,
@@ -3539,7 +3513,7 @@ pub fn resolve(this: *Lockfile, package_name: []const u8, version: Dependency.Ve
.PackageID => |id| {
const resolutions = this.packages.items(.resolution);
- if (can_satisfy and version.value.npm.satisfies(resolutions[id].value.npm.version)) {
+ if (can_satisfy and version.value.npm.version.satisfies(resolutions[id].value.npm.version)) {
return id;
}
},
@@ -3554,7 +3528,7 @@ pub fn resolve(this: *Lockfile, package_name: []const u8, version: Dependency.Ve
if (id == invalid_package_id - 1) return null;
- if (can_satisfy and version.value.npm.satisfies(resolutions[id].value.npm.version)) {
+ if (can_satisfy and version.value.npm.version.satisfies(resolutions[id].value.npm.version)) {
return id;
}
}
diff --git a/src/install/resolvers/folder_resolver.zig b/src/install/resolvers/folder_resolver.zig
index 4d0391b64..7db777d85 100644
--- a/src/install/resolvers/folder_resolver.zig
+++ b/src/install/resolvers/folder_resolver.zig
@@ -216,7 +216,7 @@ pub const FolderResolution = union(Tag) {
version,
Features.npm,
CacheFolderResolver,
- CacheFolderResolver{ .version = version.value.npm.toVersion() },
+ CacheFolderResolver{ .version = version.value.npm.version.toVersion() },
),
} catch |err| {
if (err == error.FileNotFound) {
diff --git a/src/resolver/package_json.zig b/src/resolver/package_json.zig
index 8a787c854..9a0aea6a2 100644
--- a/src/resolver/package_json.zig
+++ b/src/resolver/package_json.zig
@@ -760,8 +760,8 @@ pub const PackageJSON = struct {
if (tag == .npm) {
const sliced = Semver.SlicedString.init(package_json.version, package_json.version);
- if (Dependency.parseWithTag(r.allocator, package_json.version, .npm, &sliced, r.log)) |dependency_version| {
- if (dependency_version.value.npm.isExact()) {
+ if (Dependency.parseWithTag(r.allocator, String.init(package_json.name, package_json.name), package_json.version, .npm, &sliced, r.log)) |dependency_version| {
+ if (dependency_version.value.npm.version.isExact()) {
if (pm.lockfile.resolve(package_json.name, dependency_version)) |resolved| {
package_json.package_manager_package_id = resolved;
if (resolved > 0) {
@@ -870,20 +870,22 @@ pub const PackageJSON = struct {
if (group_json.data == .e_object) {
var group_obj = group_json.data.e_object;
for (group_obj.properties.slice()) |*prop| {
- const name = prop.key orelse continue;
- const name_str = name.asString(r.allocator) orelse continue;
+ const name_prop = prop.key orelse continue;
+ const name_str = name_prop.asString(r.allocator) orelse continue;
+ const name = String.init(name_str, name_str);
const version_value = prop.value orelse continue;
const version_str = version_value.asString(r.allocator) orelse continue;
const sliced_str = Semver.SlicedString.init(version_str, version_str);
if (Dependency.parse(
r.allocator,
+ name,
version_str,
&sliced_str,
r.log,
)) |dependency_version| {
const dependency = Dependency{
- .name = String.init(name_str, name_str),
+ .name = name,
.version = dependency_version,
.name_hash = bun.hash(name_str),
.behavior = group.behavior,
diff --git a/src/resolver/resolver.zig b/src/resolver/resolver.zig
index f9ccf014d..540e61f0b 100644
--- a/src/resolver/resolver.zig
+++ b/src/resolver/resolver.zig
@@ -1578,6 +1578,7 @@ pub const Resolver = struct {
}
dependency_version = Dependency.parse(
r.allocator,
+ Semver.String.init(esm.name, esm.name),
esm.version,
&sliced_string,
r.log,
@@ -1829,9 +1830,7 @@ pub const Resolver = struct {
if (is_main) {
if (package_json_) |package_json| {
package = Package.fromPackageJSON(
- pm.allocator,
pm.lockfile,
- r.log,
package_json,
Install.Features{
.dev_dependencies = true,
@@ -1842,12 +1841,6 @@ pub const Resolver = struct {
) catch |err| {
return .{ .failure = err };
};
-
- package.resolution = .{
- .tag = .root,
- .value = .{ .root = {} },
- };
-
package = pm.lockfile.appendPackage(package) catch |err| {
return .{ .failure = err };
};
diff --git a/test/bun.js/install/bun-install.test.ts b/test/bun.js/install/bun-install.test.ts
index 8de41fbc8..980c5ca18 100644
--- a/test/bun.js/install/bun-install.test.ts
+++ b/test/bun.js/install/bun-install.test.ts
@@ -15,6 +15,12 @@ import { bunEnv } from "bunEnv";
let handler, package_dir, requested, server;
+async function readdirSorted(path: PathLike): Promise<string[]> {
+ const results = await readdir(path);
+ results.sort();
+ return results;
+}
+
function resetHanlder() {
handler = function () {
return new Response("Tea Break~", { status: 418 });
@@ -78,7 +84,9 @@ it("should handle missing package", async () => {
);
expect(stdout).toBeDefined();
expect(await new Response(stdout).text()).toBe("");
- expect(urls).toContain("http://localhost:54321/foo");
+ expect(urls).toEqual([
+ "http://localhost:54321/foo",
+ ]);
expect(await exited).toBe(1);
expect(requested).toBe(1);
});
@@ -122,7 +130,9 @@ it("should handle @scoped authentication", async () => {
expect(err.split(/\r?\n/)).toContain(`GET ${url} - 555`);
expect(stdout).toBeDefined();
expect(await new Response(stdout).text()).toBe("");
- expect(urls).toContain(url);
+ expect(urls).toEqual([
+ url,
+ ]);
expect(seen_token).toBe(true);
expect(await exited).toBe(1);
expect(requested).toBe(1);
@@ -353,7 +363,7 @@ it("should handle inter-dependency between workspaces (optionalDependencies)", a
]);
expect(await exited).toBe(0);
expect(requested).toBe(0);
- expect(await await readdirSorted(join(package_dir, "node_modules"))).toEqual([
+ expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([
"Bar",
"Baz",
]);
@@ -477,10 +487,134 @@ it("should handle life-cycle scripts within workspaces", async () => {
);
});
-var readdirSorted = async (
- ...args: Parameters<typeof readdir>
-): ReturnType<typeof readdir> => {
- const results = await readdir(...args);
- results.sort();
- return results;
-};
+it("should handle dependency aliasing", async () => {
+ const urls: string[] = [];
+ handler = async (request) => {
+ expect(request.method).toBe("GET");
+ expect(request.headers.get("accept")).toBe(
+ "application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8, */*",
+ );
+ expect(request.headers.get("npm-auth-type")).toBe(null);
+ expect(await request.text()).toBe("");
+ urls.push(request.url);
+ return new Response("not to be found", { status: 404 });
+ };
+ await writeFile(
+ join(package_dir, "package.json"),
+ JSON.stringify({
+ name: "Foo",
+ version: "0.0.1",
+ dependencies: {
+ "Bar": "npm:baz",
+ },
+ }),
+ );
+ const { stdout, stderr, exited } = spawn({
+ cmd: [bunExe(), "install", "--config", import.meta.dir + "/basic.toml"],
+ cwd: package_dir,
+ stdout: null,
+ stdin: "pipe",
+ stderr: "pipe",
+ env,
+ });
+ expect(stderr).toBeDefined();
+ const err = await new Response(stderr).text();
+ expect(err).toContain('error: package "baz" not found localhost/baz 404');
+ expect(err).toContain("error: Bar@npm:baz failed to resolve");
+ expect(stdout).toBeDefined();
+ const out = await new Response(stdout).text();
+ expect(out.replace(/\s*\[[0-9\.]+ms\]\s*$/, "").split(/\r?\n/)).toEqual([""]);
+ expect(urls).toEqual([
+ "http://localhost:54321/baz",
+ ]);
+ expect(await exited).toBe(1);
+ expect(requested).toBe(1);
+});
+
+it("should handle dependency aliasing (versioned)", async () => {
+ const urls: string[] = [];
+ handler = async (request) => {
+ expect(request.method).toBe("GET");
+ expect(request.headers.get("accept")).toBe(
+ "application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8, */*",
+ );
+ expect(request.headers.get("npm-auth-type")).toBe(null);
+ expect(await request.text()).toBe("");
+ urls.push(request.url);
+ return new Response("not to be found", { status: 404 });
+ };
+ await writeFile(
+ join(package_dir, "package.json"),
+ JSON.stringify({
+ name: "Foo",
+ version: "0.0.1",
+ dependencies: {
+ "Bar": "npm:baz@0.0.2",
+ },
+ }),
+ );
+ const { stdout, stderr, exited } = spawn({
+ cmd: [bunExe(), "install", "--config", import.meta.dir + "/basic.toml"],
+ cwd: package_dir,
+ stdout: null,
+ stdin: "pipe",
+ stderr: "pipe",
+ env,
+ });
+ expect(stderr).toBeDefined();
+ const err = await new Response(stderr).text();
+ expect(err).toContain('error: package "baz" not found localhost/baz 404');
+ expect(err).toContain("error: Bar@npm:baz@0.0.2 failed to resolve");
+ expect(stdout).toBeDefined();
+ const out = await new Response(stdout).text();
+ expect(out.replace(/\s*\[[0-9\.]+ms\]\s*$/, "").split(/\r?\n/)).toEqual([""]);
+ expect(urls).toEqual([
+ "http://localhost:54321/baz",
+ ]);
+ expect(await exited).toBe(1);
+ expect(requested).toBe(1);
+});
+
+it("should handle dependency aliasing (dist-tagged)", async () => {
+ const urls: string[] = [];
+ handler = async (request) => {
+ expect(request.method).toBe("GET");
+ expect(request.headers.get("accept")).toBe(
+ "application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8, */*",
+ );
+ expect(request.headers.get("npm-auth-type")).toBe(null);
+ expect(await request.text()).toBe("");
+ urls.push(request.url);
+ return new Response("not to be found", { status: 404 });
+ };
+ await writeFile(
+ join(package_dir, "package.json"),
+ JSON.stringify({
+ name: "Foo",
+ version: "0.0.1",
+ dependencies: {
+ "Bar": "npm:baz@latest",
+ },
+ }),
+ );
+ const { stdout, stderr, exited } = spawn({
+ cmd: [bunExe(), "install", "--config", import.meta.dir + "/basic.toml"],
+ cwd: package_dir,
+ stdout: null,
+ stdin: "pipe",
+ stderr: "pipe",
+ env,
+ });
+ expect(stderr).toBeDefined();
+ const err = await new Response(stderr).text();
+ expect(err).toContain('error: package "baz" not found localhost/baz 404');
+ expect(err).toContain("error: Bar@npm:baz@latest failed to resolve");
+ expect(stdout).toBeDefined();
+ const out = await new Response(stdout).text();
+ expect(out.replace(/\s*\[[0-9\.]+ms\]\s*$/, "").split(/\r?\n/)).toEqual([""]);
+ expect(urls).toEqual([
+ "http://localhost:54321/baz",
+ ]);
+ expect(await exited).toBe(1);
+ expect(requested).toBe(1);
+});