mirror of
https://github.com/oven-sh/bun
synced 2026-02-17 14:22:01 +00:00
fix(install): exclude optional peers resolved against dev deps in --prod
When running `bun install --production`, optional peer dependencies of production packages (e.g. typescript as an optional peer of @prisma/client) were incorrectly installed if they also appeared in the root devDependencies. During the `.resolvable` tree-building phase, optional peers get resolved against any matching dependency in the tree, including dev deps. In the subsequent `.filter` phase, the root dev dependency is correctly filtered out, but the optional peer retains its resolved package ID and passes through the filter since remote_package_features.peer_dependencies is true. The fix adds a check in the filter phase: when an optional peer dependency from a non-root package matches a root dev dependency and dev_dependencies are disabled (--prod mode), it is filtered out. Closes #26837 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -394,6 +394,21 @@ pub fn isFilteredDependencyOrWorkspace(
|
||||
// Filtering only applies to the root package dependencies. Also
|
||||
// --filter has a different meaning if a new package is being installed.
|
||||
if (manager.subcommand != .install or parent_pkg_id != 0) {
|
||||
// Optional peer dependencies can get resolved against root dev
|
||||
// dependencies during the `.resolvable` phase. In `--prod` mode
|
||||
// (dev_dependencies disabled), these optional peers should not
|
||||
// pull in packages that would otherwise only exist as dev deps.
|
||||
if (parent_pkg_id != 0 and dep.behavior.isOptionalPeer() and
|
||||
!manager.options.local_package_features.dev_dependencies)
|
||||
{
|
||||
const root_dep_list = pkgs.items(.dependencies)[0];
|
||||
const root_deps = lockfile.buffers.dependencies.items[root_dep_list.begin()..root_dep_list.end()];
|
||||
for (root_deps) |root_dep| {
|
||||
if (root_dep.name_hash == dep.name_hash and root_dep.behavior.isDev()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
45
test/regression/issue/26837.test.ts
Normal file
45
test/regression/issue/26837.test.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
// https://github.com/oven-sh/bun/issues/26837
|
||||
// Test that `bun install --production` does not install optional peer
|
||||
// dependencies that are also listed as devDependencies. When a production
|
||||
// dependency (e.g. @prisma/client) has an optional peer (e.g. typescript),
|
||||
// and that same package appears in the root devDependencies, `--production`
|
||||
// should NOT install it.
|
||||
|
||||
import { expect, test } from "bun:test";
|
||||
import { bunEnv, bunExe, tempDir } from "harness";
|
||||
|
||||
test("--production should not install optional peers that match devDependencies", async () => {
|
||||
using dir = tempDir("issue-26837", {
|
||||
"package.json": JSON.stringify({
|
||||
name: "test-issue-26837",
|
||||
dependencies: {
|
||||
"@prisma/client": "^6.3.1",
|
||||
},
|
||||
devDependencies: {
|
||||
typescript: "^5.7.3",
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
// Run bun install --production
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "install", "--production"],
|
||||
cwd: String(dir),
|
||||
env: bunEnv,
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||||
|
||||
// typescript should NOT be installed since it's a devDependency
|
||||
// and we're installing with --production
|
||||
const output = stdout + stderr;
|
||||
expect(output).not.toContain("error:");
|
||||
|
||||
// Check that typescript is not in node_modules
|
||||
const typescriptExists = await Bun.file(`${dir}/node_modules/typescript/package.json`).exists();
|
||||
expect(typescriptExists).toBe(false);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
});
|
||||
Reference in New Issue
Block a user