diff options
author | 2023-05-01 06:41:33 +0300 | |
---|---|---|
committer | 2023-05-01 06:41:33 +0300 | |
commit | c05a6744bb3b979272767c20b6ffe055a18a3c0c (patch) | |
tree | 9e988bd11f3d8f409edb9ca4fff2a9fcee119bc0 | |
parent | f54fbaf3ba2938b4d82de08bdcb0f920034ec8bb (diff) | |
download | bun-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.zig | 2 | ||||
-rw-r--r-- | src/install/install.zig | 88 | ||||
-rw-r--r-- | src/resolver/resolver.zig | 2 | ||||
-rw-r--r-- | test/cli/install/bun-install.test.ts | 106 |
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")); +}); |