aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Alex Lam S.L <alexlamsl@gmail.com> 2023-05-01 06:41:33 +0300
committerGravatar GitHub <noreply@github.com> 2023-05-01 06:41:33 +0300
commitc05a6744bb3b979272767c20b6ffe055a18a3c0c (patch)
tree9e988bd11f3d8f409edb9ca4fff2a9fcee119bc0
parentf54fbaf3ba2938b4d82de08bdcb0f920034ec8bb (diff)
downloadbun-c05a6744bb3b979272767c20b6ffe055a18a3c0c.tar.gz
bun-c05a6744bb3b979272767c20b6ffe055a18a3c0c.tar.zst
bun-c05a6744bb3b979272767c20b6ffe055a18a3c0c.zip
[install] handle `devDependencies` of local folders (#2781)
fixes #2653
-rw-r--r--src/bun.js/module_loader.zig2
-rw-r--r--src/install/install.zig88
-rw-r--r--src/resolver/resolver.zig2
-rw-r--r--test/cli/install/bun-install.test.ts106
4 files changed, 133 insertions, 65 deletions
diff --git a/src/bun.js/module_loader.zig b/src/bun.js/module_loader.zig
index 7e20e61bc..131ac5b59 100644
--- a/src/bun.js/module_loader.zig
+++ b/src/bun.js/module_loader.zig
@@ -446,7 +446,7 @@ pub const ModuleLoader = struct {
// we are only truly done if all the dependencies are done.
const current_tasks = pm.total_tasks;
// so if enqueuing all the dependencies produces no new tasks, we are done.
- pm.enqueueDependencyList(package.dependencies, false);
+ pm.enqueueDependencyList(package.dependencies);
if (current_tasks == pm.total_tasks) {
tags[tag_i] = .done;
done_count += 1;
diff --git a/src/install/install.zig b/src/install/install.zig
index 4bad3eafa..526b540ee 100644
--- a/src/install/install.zig
+++ b/src/install/install.zig
@@ -1704,7 +1704,6 @@ pub const PackageManager = struct {
version_buf: []const u8,
version: *const Dependency.Version,
behavior: Dependency.Behavior,
- is_main: bool,
) DependencyToEnqueue {
const str_buf = this.lockfile.buffers.string_bytes.items;
for (this.lockfile.buffers.dependencies.items, 0..) |dependency, dependency_id| {
@@ -1740,29 +1739,15 @@ pub const PackageManager = struct {
this.lockfile.buffers.dependencies.append(this.allocator, cloned_dependency) catch unreachable;
this.lockfile.buffers.resolutions.append(this.allocator, invalid_package_id) catch unreachable;
if (comptime Environment.allow_assert) std.debug.assert(this.lockfile.buffers.dependencies.items.len == this.lockfile.buffers.resolutions.items.len);
- if (is_main) {
- this.enqueueDependencyWithMainAndSuccessFn(
- index,
- &cloned_dependency,
- invalid_package_id,
- true,
- assignRootResolution,
- failRootResolution,
- ) catch |err| {
- return .{ .failure = err };
- };
- } else {
- this.enqueueDependencyWithMainAndSuccessFn(
- index,
- &cloned_dependency,
- invalid_package_id,
- false,
- assignRootResolution,
- failRootResolution,
- ) catch |err| {
- return .{ .failure = err };
- };
- }
+ this.enqueueDependencyWithMainAndSuccessFn(
+ index,
+ &cloned_dependency,
+ invalid_package_id,
+ assignRootResolution,
+ failRootResolution,
+ ) catch |err| {
+ return .{ .failure = err };
+ };
const resolution_id = this.lockfile.buffers.resolutions.items[index];
@@ -2302,11 +2287,11 @@ pub const PackageManager = struct {
};
switch (FolderResolution.getOrPut(.{ .cache_folder = npm_package_path }, dependency, ".", this)) {
.new_package_id => |id| {
- this.enqueueDependencyList(this.lockfile.packages.items(.dependencies)[id], false);
+ this.enqueueDependencyList(this.lockfile.packages.items(.dependencies)[id]);
return id;
},
.package_id => |id| {
- this.enqueueDependencyList(this.lockfile.packages.items(.dependencies)[id], false);
+ this.enqueueDependencyList(this.lockfile.packages.items(.dependencies)[id]);
return id;
},
.err => |err| {
@@ -2800,13 +2785,11 @@ pub const PackageManager = struct {
/// This must be a *const to prevent UB
dependency: *const Dependency,
resolution: PackageID,
- comptime is_main: bool,
) !void {
return this.enqueueDependencyWithMainAndSuccessFn(
id,
dependency,
resolution,
- is_main,
assignResolution,
null,
);
@@ -2814,13 +2797,12 @@ pub const PackageManager = struct {
/// Q: "What do we do with a dependency in a package.json?"
/// A: "We enqueue it!"
- pub fn enqueueDependencyWithMainAndSuccessFn(
+ fn enqueueDependencyWithMainAndSuccessFn(
this: *PackageManager,
id: DependencyID,
/// This must be a *const to prevent UB
dependency: *const Dependency,
resolution: PackageID,
- comptime is_main: bool,
comptime successFn: SuccessFn,
comptime failFn: ?FailFn,
) !void {
@@ -2833,16 +2815,6 @@ pub const PackageManager = struct {
const version = dependency.version;
var loaded_manifest: ?Npm.PackageManifest = null;
- if (comptime !is_main) {
- // it might really be main
- if (!this.isRootDependency(id))
- if (!dependency.behavior.isEnabled(switch (dependency.version.tag) {
- .dist_tag, .folder, .npm => this.options.remote_package_features,
- else => .{},
- }))
- return;
- }
-
switch (dependency.version.tag) {
.dist_tag, .folder, .npm => {
retry_from_manifests_ptr: while (true) {
@@ -3286,7 +3258,6 @@ pub const PackageManager = struct {
i,
&dependency,
lockfile.buffers.resolutions.items[i],
- false,
) catch {};
}
}
@@ -3321,26 +3292,22 @@ pub const PackageManager = struct {
pub fn enqueueDependencyList(
this: *PackageManager,
dependencies_list: Lockfile.DependencySlice,
- comptime is_main: bool,
) void {
this.task_queue.ensureUnusedCapacity(this.allocator, dependencies_list.len) catch unreachable;
- var lockfile = this.lockfile;
+ const lockfile = this.lockfile;
// Step 1. Go through main dependencies
- {
- var i = dependencies_list.off;
- const end = dependencies_list.off +| dependencies_list.len;
- // we have to be very careful with pointers here
- while (i < end) : (i += 1) {
- const dependency = lockfile.buffers.dependencies.items[i];
- const resolution = lockfile.buffers.resolutions.items[i];
- this.enqueueDependencyWithMain(
- i,
- &dependency,
- resolution,
- is_main,
- ) catch {};
- }
+ var i = dependencies_list.off;
+ const end = dependencies_list.off +| dependencies_list.len;
+ // we have to be very careful with pointers here
+ while (i < end) : (i += 1) {
+ const dependency = lockfile.buffers.dependencies.items[i];
+ const resolution = lockfile.buffers.resolutions.items[i];
+ this.enqueueDependencyWithMain(
+ i,
+ &dependency,
+ resolution,
+ ) catch {};
}
this.drainDependencyList();
@@ -3366,10 +3333,8 @@ pub const PackageManager = struct {
dependency_id,
&dependency,
resolution,
- false,
);
},
-
.root_dependency => |dependency_id| {
const dependency = this.lockfile.buffers.dependencies.items[dependency_id];
const resolution = this.lockfile.buffers.resolutions.items[dependency_id];
@@ -3378,11 +3343,9 @@ pub const PackageManager = struct {
dependency_id,
&dependency,
resolution,
- true,
assignRootResolution,
failRootResolution,
);
-
if (any_root) |ptr| {
const new_resolution_id = this.lockfile.buffers.resolutions.items[dependency_id];
if (new_resolution_id != resolution) {
@@ -7349,7 +7312,6 @@ pub const PackageManager = struct {
dependency_i,
&dependency,
manager.lockfile.buffers.resolutions.items[dependency_i],
- true,
);
}
}
@@ -7387,7 +7349,7 @@ pub const PackageManager = struct {
_ = manager.getCacheDirectory();
_ = manager.getTemporaryDirectory();
}
- manager.enqueueDependencyList(root.dependencies, true);
+ manager.enqueueDependencyList(root.dependencies);
} else {
// Anything that needs to be downloaded from an update needs to be scheduled here
manager.drainDependencyList();
diff --git a/src/resolver/resolver.zig b/src/resolver/resolver.zig
index 48c6b2889..3fc38f900 100644
--- a/src/resolver/resolver.zig
+++ b/src/resolver/resolver.zig
@@ -2046,7 +2046,7 @@ pub const Resolver = struct {
// All packages are enqueued to the root
// because we download all the npm package dependencies
- switch (pm.enqueueDependencyToRoot(esm.name, esm.version, &version, behavior, is_main)) {
+ switch (pm.enqueueDependencyToRoot(esm.name, esm.version, &version, behavior)) {
.resolution => |result| {
input_package_id_.* = result.package_id;
return .{ .resolution = result.resolution };
diff --git a/test/cli/install/bun-install.test.ts b/test/cli/install/bun-install.test.ts
index a5a7c9057..9833ab3f4 100644
--- a/test/cli/install/bun-install.test.ts
+++ b/test/cli/install/bun-install.test.ts
@@ -3830,3 +3830,109 @@ it("should handle tarball path with existing lockfile", async () => {
});
await access(join(package_dir, "bun.lockb"));
});
+
+it("should handle devDependencies from folder", async () => {
+ const urls: string[] = [];
+ setHandler(dummyRegistry(urls));
+ await writeFile(
+ join(package_dir, "package.json"),
+ JSON.stringify({
+ name: "foo",
+ version: "0.1.0",
+ dependencies: {
+ moo: "file:./moo",
+ },
+ }),
+ );
+ await mkdir(join(package_dir, "moo"));
+ const moo_package = JSON.stringify({
+ name: "moo",
+ version: "0.2.0",
+ devDependencies: {
+ bar: "^0.0.2",
+ },
+ });
+ await writeFile(join(package_dir, "moo", "package.json"), moo_package);
+ const { stdout, stderr, exited } = spawn({
+ cmd: [bunExe(), "install"],
+ cwd: package_dir,
+ stdout: null,
+ stdin: "pipe",
+ stderr: "pipe",
+ env,
+ });
+ expect(stderr).toBeDefined();
+ const err = await new Response(stderr).text();
+ expect(err).toContain("Saved lockfile");
+ expect(stdout).toBeDefined();
+ const out = await new Response(stdout).text();
+ expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([" + moo@moo", "", " 2 packages installed"]);
+ expect(await exited).toBe(0);
+ expect(urls.sort()).toEqual([`${root_url}/bar`, `${root_url}/bar-0.0.2.tgz`]);
+ expect(requested).toBe(2);
+ expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".cache", "bar", "moo"]);
+ expect(await readdirSorted(join(package_dir, "node_modules", "bar"))).toEqual(["package.json"]);
+ expect(await file(join(package_dir, "node_modules", "bar", "package.json")).json()).toEqual({
+ name: "bar",
+ version: "0.0.2",
+ });
+ expect(await readdirSorted(join(package_dir, "node_modules", "moo"))).toEqual(["package.json"]);
+ expect(await file(join(package_dir, "node_modules", "moo", "package.json")).text()).toEqual(moo_package);
+ await access(join(package_dir, "bun.lockb"));
+});
+
+it("should deduplicate devDependencies from folder", async () => {
+ const urls: string[] = [];
+ setHandler(dummyRegistry(urls));
+ await writeFile(
+ join(package_dir, "package.json"),
+ JSON.stringify({
+ name: "foo",
+ version: "0.1.0",
+ devDependencies: {
+ bar: "^0.0.2",
+ moo: "file:./moo",
+ },
+ }),
+ );
+ await mkdir(join(package_dir, "moo"));
+ const moo_package = JSON.stringify({
+ name: "moo",
+ version: "0.2.0",
+ devDependencies: {
+ bar: "^0.0.2",
+ },
+ });
+ await writeFile(join(package_dir, "moo", "package.json"), moo_package);
+ const { stdout, stderr, exited } = spawn({
+ cmd: [bunExe(), "install"],
+ cwd: package_dir,
+ stdout: null,
+ stdin: "pipe",
+ stderr: "pipe",
+ env,
+ });
+ expect(stderr).toBeDefined();
+ const err = await new Response(stderr).text();
+ expect(err).toContain("Saved lockfile");
+ expect(stdout).toBeDefined();
+ const out = await new Response(stdout).text();
+ expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([
+ " + bar@0.0.2",
+ " + moo@moo",
+ "",
+ " 2 packages installed",
+ ]);
+ expect(await exited).toBe(0);
+ expect(urls.sort()).toEqual([`${root_url}/bar`, `${root_url}/bar-0.0.2.tgz`]);
+ expect(requested).toBe(2);
+ expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".cache", "bar", "moo"]);
+ expect(await readdirSorted(join(package_dir, "node_modules", "bar"))).toEqual(["package.json"]);
+ expect(await file(join(package_dir, "node_modules", "bar", "package.json")).json()).toEqual({
+ name: "bar",
+ version: "0.0.2",
+ });
+ expect(await readdirSorted(join(package_dir, "node_modules", "moo"))).toEqual(["package.json"]);
+ expect(await file(join(package_dir, "node_modules", "moo", "package.json")).text()).toEqual(moo_package);
+ await access(join(package_dir, "bun.lockb"));
+});