From 67e0f8931a2a9ea21185fb0abe70dd28dfc2a42a Mon Sep 17 00:00:00 2001 From: Claude Bot Date: Mon, 12 Jan 2026 11:58:45 +0000 Subject: [PATCH] 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 --- src/install/lockfile/Package/WorkspaceMap.zig | 2 +- test/regression/issue/25801.test.ts | 96 +++++++++++++++++++ 2 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 test/regression/issue/25801.test.ts diff --git a/src/install/lockfile/Package/WorkspaceMap.zig b/src/install/lockfile/Package/WorkspaceMap.zig index 8003cd5dfb..a802406dd5 100644 --- a/src/install/lockfile/Package/WorkspaceMap.zig +++ b/src/install/lockfile/Package/WorkspaceMap.zig @@ -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, diff --git a/test/regression/issue/25801.test.ts b/test/regression/issue/25801.test.ts new file mode 100644 index 0000000000..7b85988b17 --- /dev/null +++ b/test/regression/issue/25801.test.ts @@ -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); +});