From ef35fd8f4c68b11b2c4c307f65dc8bd7ef4498c2 Mon Sep 17 00:00:00 2001 From: shadcn Date: Mon, 16 Mar 2026 13:27:13 +0400 Subject: [PATCH] fix: refactor --- packages/shadcn/src/utils/resolve-import.ts | 2 + .../utils/transformers/transform-import.ts | 6 +++ .../src/utils/workspace-package-exports.ts | 37 ++++++++++++++++++- .../shadcn/test/utils/resolve-import.test.ts | 7 ++++ .../test/utils/transform-import.test.ts | 23 ++++++++++++ 5 files changed, 74 insertions(+), 1 deletion(-) diff --git a/packages/shadcn/src/utils/resolve-import.ts b/packages/shadcn/src/utils/resolve-import.ts index 62f1c35b2a..25acf89429 100644 --- a/packages/shadcn/src/utils/resolve-import.ts +++ b/packages/shadcn/src/utils/resolve-import.ts @@ -92,6 +92,8 @@ export function isLocalAliasImport( moduleSpecifier: string, aliasPrefix: string | null ) { + // Workspace package exports such as `@workspace/ui/...` are already the final + // import specifiers we want to keep, so they are intentionally excluded here. if (moduleSpecifier.startsWith("#")) { return true } diff --git a/packages/shadcn/src/utils/transformers/transform-import.ts b/packages/shadcn/src/utils/transformers/transform-import.ts index 1b8ae160c2..ecd5e15e63 100644 --- a/packages/shadcn/src/utils/transformers/transform-import.ts +++ b/packages/shadcn/src/utils/transformers/transform-import.ts @@ -156,6 +156,12 @@ function updateImportAliases( } function getWorkspaceAliasFromUtilsAlias(utilsAlias: string) { + // `#...` utils aliases are handled by package-import normalization and should + // not be treated as workspace package roots. + if (utilsAlias.startsWith("#")) { + return "" + } + if (utilsAlias.endsWith("/lib/utils")) { return utilsAlias.slice(0, -"/lib/utils".length) } diff --git a/packages/shadcn/src/utils/workspace-package-exports.ts b/packages/shadcn/src/utils/workspace-package-exports.ts index 71e047bec6..e02465cf00 100644 --- a/packages/shadcn/src/utils/workspace-package-exports.ts +++ b/packages/shadcn/src/utils/workspace-package-exports.ts @@ -3,6 +3,7 @@ import { getWorkspacePatterns } from "@/src/utils/get-monorepo-info" import { getPackageInfo } from "@/src/utils/get-package-info" import { type ImportEmitMode } from "@/src/utils/package-imports" import fg from "fast-glob" +import fs from "fs-extra" type WorkspacePackageInfo = { packageName: string @@ -33,6 +34,7 @@ const workspaceExportEntriesCache = new Map< string, WorkspacePackageExportEntry[] >() +const workspaceRootCache = new Map() export async function resolveWorkspacePackageExport( importPath: string, @@ -203,12 +205,45 @@ async function loadWorkspacePackages(root: string) { } async function findWorkspaceRoot(cwd: string) { - let current = path.resolve(cwd) + const start = path.resolve(cwd) + const cachedRoot = workspaceRootCache.get(start) + + if (cachedRoot !== undefined) { + return cachedRoot + } + + let current = start + const gitRoot = await findGitRoot(start) while (true) { const patterns = await getWorkspacePatterns(current) if (patterns.length) { + workspaceRootCache.set(start, current) + return current + } + + if (gitRoot && current === gitRoot) { + workspaceRootCache.set(start, null) + return null + } + + const parent = path.dirname(current) + + if (parent === current) { + workspaceRootCache.set(start, null) + return null + } + + current = parent + } +} + +async function findGitRoot(cwd: string) { + let current = path.resolve(cwd) + + while (true) { + if (fs.existsSync(path.resolve(current, ".git"))) { return current } diff --git a/packages/shadcn/test/utils/resolve-import.test.ts b/packages/shadcn/test/utils/resolve-import.test.ts index d683222085..43a855fb1a 100644 --- a/packages/shadcn/test/utils/resolve-import.test.ts +++ b/packages/shadcn/test/utils/resolve-import.test.ts @@ -3,6 +3,7 @@ import { loadConfig, type ConfigLoaderSuccessResult } from "tsconfig-paths" import { describe, expect, test } from "vitest" import { + isLocalAliasImport, resolveImport, resolveImportWithMetadata, } from "../../src/utils/resolve-import" @@ -186,4 +187,10 @@ describe("resolve workspace package exports", () => { path.resolve(root, "packages/ui/src/lib/utils.ts") ) }) + + test("does not treat workspace package exports as local alias imports", () => { + expect(isLocalAliasImport("@workspace/ui/components/button", "#")).toBe( + false + ) + }) }) diff --git a/packages/shadcn/test/utils/transform-import.test.ts b/packages/shadcn/test/utils/transform-import.test.ts index 674d03e2c2..4335c6806a 100644 --- a/packages/shadcn/test/utils/transform-import.test.ts +++ b/packages/shadcn/test/utils/transform-import.test.ts @@ -200,6 +200,29 @@ import { cn } from "#app/lib/utils" `) }) +test("transform import keeps exact #utils aliases", async () => { + expect( + await transform({ + filename: "test.ts", + raw: `import { cn } from "@/lib/utils" +`, + config: { + tsx: true, + aliases: { + components: "#components", + utils: "#utils", + ui: "#components/ui", + lib: "#lib", + hooks: "#hooks", + }, + }, + }) + ).toMatchInlineSnapshot(` + "import { cn } from "#utils" + " + `) +}) + test("transform import for monorepo", async () => { expect( await transform({