fix(install): follow symlinks when resolving workspace glob patterns

When using glob patterns like `./*` or `packages/*` in the workspaces array,
symlinked directories pointing to packages outside the monorepo were not
being discovered. This was because the GlobWalker was initialized with
`follow_symlinks: false`.

This change enables symlink following in the workspace glob walker, allowing
workspace packages that are symlinks to be properly discovered and resolved.

Fixes #25801

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Claude Bot
2026-01-12 11:58:45 +00:00
parent beccd01647
commit 67e0f8931a
2 changed files with 97 additions and 1 deletions

View File

@@ -226,7 +226,7 @@ pub fn processNamesArray(
var walker: GlobWalker = .{};
var cwd = bun.path.dirname(source.path.text, .auto);
cwd = if (bun.strings.eql(cwd, "")) bun.fs.FileSystem.instance.top_level_dir else cwd;
if ((try walker.initWithCwd(&arena, glob_pattern, cwd, false, false, false, false, true)).asErr()) |e| {
if ((try walker.initWithCwd(&arena, glob_pattern, cwd, false, false, true, false, true)).asErr()) |e| {
log.addErrorFmt(
source,
loc,

View File

@@ -0,0 +1,96 @@
// https://github.com/oven-sh/bun/issues/25801
// Workspace packages that are symlinks to directories outside the monorepo
// should be discovered during `bun install` when using glob patterns.
import { expect, test } from "bun:test";
import { symlinkSync } from "fs";
import { bunEnv, bunExe, tempDir } from "harness";
import { join } from "path";
test("workspace glob patterns should follow symlinks to external directories", async () => {
// Create a temporary directory for the external package (outside the monorepo)
using externalPkgDir = tempDir("external-pkg", {
"package.json": JSON.stringify({
name: "backend",
version: "1.0.0",
}),
});
// Create the monorepo with a glob pattern in workspaces
using monorepoDir = tempDir("monorepo", {
"package.json": JSON.stringify({
name: "monorepo-test",
workspaces: ["./*"],
dependencies: {
backend: "workspace:*",
},
}),
});
// Create a symlink inside the monorepo pointing to the external package
symlinkSync(String(externalPkgDir), join(String(monorepoDir), "backend"));
// Run bun install
await using proc = Bun.spawn({
cmd: [bunExe(), "install"],
cwd: String(monorepoDir),
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
// Should not fail with "Workspace dependency 'backend' not found"
expect(stderr).not.toContain("Workspace dependency");
expect(stderr).not.toContain("not found");
// Should succeed
expect(exitCode).toBe(0);
});
test("workspace glob patterns should follow symlinks in packages directory", async () => {
// Create an external package to be symlinked
using externalPkgDir = tempDir("external-alias-pkg", {
"package.json": JSON.stringify({
name: "pkg-alias",
version: "2.0.0",
}),
});
// Create a monorepo with packages/* glob pattern
using monorepoDir = tempDir("monorepo-internal", {
"package.json": JSON.stringify({
name: "monorepo-internal",
workspaces: ["packages/*"],
dependencies: {
"pkg-alias": "workspace:*",
},
}),
"packages/real-pkg/package.json": JSON.stringify({
name: "real-pkg",
version: "1.0.0",
}),
});
// Create a symlink to the external package inside packages/
symlinkSync(String(externalPkgDir), join(String(monorepoDir), "packages", "pkg-alias"));
// Run bun install
await using proc = Bun.spawn({
cmd: [bunExe(), "install"],
cwd: String(monorepoDir),
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
// Should not fail with workspace dependency errors
expect(stderr).not.toContain("Workspace dependency");
expect(stderr).not.toContain("not found");
// Should succeed
expect(exitCode).toBe(0);
});