From f07c7ad5d0d2c2159f2c81a31ae6cbbe9a6ede30 Mon Sep 17 00:00:00 2001 From: shadcn Date: Wed, 15 Jan 2025 18:01:44 +0400 Subject: [PATCH 1/2] feat(shadcn): handle nested paths for components (#6369) * feat(shadcn): handle nested paths for components * chore: add changeset --- .changeset/silent-rules-cover.md | 5 + packages/shadcn/src/registry/api.ts | 34 -- .../src/utils/updaters/update-css-vars.ts | 2 + .../shadcn/src/utils/updaters/update-files.ts | 146 ++++- .../test/utils/updaters/update-files.test.ts | 498 ++++++++++++++++-- 5 files changed, 580 insertions(+), 105 deletions(-) create mode 100644 .changeset/silent-rules-cover.md diff --git a/.changeset/silent-rules-cover.md b/.changeset/silent-rules-cover.md new file mode 100644 index 00000000000..9e6ab3aa11b --- /dev/null +++ b/.changeset/silent-rules-cover.md @@ -0,0 +1,5 @@ +--- +"shadcn": minor +--- + +handle nested file path diff --git a/packages/shadcn/src/registry/api.ts b/packages/shadcn/src/registry/api.ts index 95e20ba4602..4ad54d218dc 100644 --- a/packages/shadcn/src/registry/api.ts +++ b/packages/shadcn/src/registry/api.ts @@ -233,40 +233,6 @@ export async function fetchRegistry(paths: string[]) { } } -export function getRegistryItemFileTargetPath( - file: z.infer, - config: Config, - override?: string -) { - if (override) { - return override - } - - if (file.type === "registry:ui") { - return config.resolvedPaths.ui - } - - if (file.type === "registry:lib") { - return config.resolvedPaths.lib - } - - if (file.type === "registry:block" || file.type === "registry:component") { - return config.resolvedPaths.components - } - - if (file.type === "registry:hook") { - return config.resolvedPaths.hooks - } - - // TODO: we put this in components for now. - // We should move this to pages as per framework. - if (file.type === "registry:page") { - return config.resolvedPaths.components - } - - return config.resolvedPaths.components -} - export async function registryResolveItemsTree( names: z.infer["name"][], config: Config diff --git a/packages/shadcn/src/utils/updaters/update-css-vars.ts b/packages/shadcn/src/utils/updaters/update-css-vars.ts index 73fd2cebebb..f362961dd4a 100644 --- a/packages/shadcn/src/utils/updaters/update-css-vars.ts +++ b/packages/shadcn/src/utils/updaters/update-css-vars.ts @@ -56,6 +56,8 @@ export async function transformCssVars( config: Config, options: { cleanupDefaultNextStyles?: boolean + } = { + cleanupDefaultNextStyles: false, } ) { options = { diff --git a/packages/shadcn/src/utils/updaters/update-files.ts b/packages/shadcn/src/utils/updaters/update-files.ts index f91921989d0..2e1df1f022d 100644 --- a/packages/shadcn/src/utils/updaters/update-files.ts +++ b/packages/shadcn/src/utils/updaters/update-files.ts @@ -1,10 +1,7 @@ import { existsSync, promises as fs } from "fs" import path, { basename } from "path" -import { - getRegistryBaseColor, - getRegistryItemFileTargetPath, -} from "@/src/registry/api" -import { RegistryItem } from "@/src/registry/schema" +import { getRegistryBaseColor } from "@/src/registry/api" +import { RegistryItem, registryItemFileSchema } from "@/src/registry/schema" import { Config } from "@/src/utils/get-config" import { getProjectInfo } from "@/src/utils/get-project-info" import { highlighter } from "@/src/utils/highlighter" @@ -17,19 +14,7 @@ import { transformImport } from "@/src/utils/transformers/transform-import" import { transformRsc } from "@/src/utils/transformers/transform-rsc" import { transformTwPrefixes } from "@/src/utils/transformers/transform-tw-prefix" import prompts from "prompts" - -export function resolveTargetDir( - projectInfo: Awaited>, - config: Config, - target: string -) { - if (target.startsWith("~/")) { - return path.join(config.resolvedPaths.cwd, target.replace("~/", "")) - } - return projectInfo?.isSrcDir - ? path.join(config.resolvedPaths.cwd, "src", target) - : path.join(config.resolvedPaths.cwd, target) -} +import { z } from "zod" export async function updateFiles( files: RegistryItem["files"], @@ -74,14 +59,15 @@ export async function updateFiles( continue } - let targetDir = getRegistryItemFileTargetPath(file, config) + let filePath = resolveFilePath(file, config, { + isSrcDir: projectInfo?.isSrcDir, + commonRoot: findCommonRoot( + files.map((f) => f.path), + file.path + ), + }) const fileName = basename(file.path) - let filePath = path.join(targetDir, fileName) - - if (file.target) { - filePath = resolveTargetDir(projectInfo, config, file.target) - targetDir = path.dirname(filePath) - } + const targetDir = path.dirname(filePath) if (!config.tsx) { filePath = filePath.replace(/\.tsx?$/, (match) => @@ -210,3 +196,113 @@ export async function updateFiles( filesSkipped, } } + +export function resolveFilePath( + file: z.infer, + config: Config, + options: { + isSrcDir?: boolean + commonRoot?: string + } +) { + if (file.target) { + if (file.target.startsWith("~/")) { + return path.join(config.resolvedPaths.cwd, file.target.replace("~/", "")) + } + + return options.isSrcDir + ? path.join( + config.resolvedPaths.cwd, + "src", + file.target.replace("src/", "") + ) + : path.join(config.resolvedPaths.cwd, file.target.replace("src/", "")) + } + + const targetDir = resolveFileTargetDirectory(file, config) + + const relativePath = resolveNestedFilePath(file.path, targetDir) + return path.join(targetDir, relativePath) +} + +function resolveFileTargetDirectory( + file: z.infer, + config: Config +) { + if (file.type === "registry:ui") { + return config.resolvedPaths.ui + } + + if (file.type === "registry:lib") { + return config.resolvedPaths.lib + } + + if (file.type === "registry:block" || file.type === "registry:component") { + return config.resolvedPaths.components + } + + if (file.type === "registry:hook") { + return config.resolvedPaths.hooks + } + + return config.resolvedPaths.components +} + +export function findCommonRoot(paths: string[], needle: string): string { + // Remove leading slashes for consistent handling + const normalizedPaths = paths.map((p) => p.replace(/^\//, "")) + const normalizedNeedle = needle.replace(/^\//, "") + + // Get the directory path of the needle by removing the file name + const needleDir = normalizedNeedle.split("/").slice(0, -1).join("/") + + // If needle is at root level, return empty string + if (!needleDir) { + return "" + } + + // Split the needle directory into segments + const needleSegments = needleDir.split("/") + + // Start from the full path and work backwards + for (let i = needleSegments.length; i > 0; i--) { + const testPath = needleSegments.slice(0, i).join("/") + // Check if this is a common root by verifying if any other paths start with it + const hasRelatedPaths = normalizedPaths.some( + (path) => path !== normalizedNeedle && path.startsWith(testPath + "/") + ) + if (hasRelatedPaths) { + return "/" + testPath // Add leading slash back for the result + } + } + + // If no common root found with other files, return the parent directory of the needle + return "/" + needleDir // Add leading slash back for the result +} + +export function resolveNestedFilePath( + filePath: string, + targetDir: string +): string { + // Normalize paths by removing leading/trailing slashes + const normalizedFilePath = filePath.replace(/^\/|\/$/g, "") + const normalizedTargetDir = targetDir.replace(/^\/|\/$/g, "") + + // Split paths into segments + const fileSegments = normalizedFilePath.split("/") + const targetSegments = normalizedTargetDir.split("/") + + // Find the last matching segment from targetDir in filePath + const lastTargetSegment = targetSegments[targetSegments.length - 1] + const commonDirIndex = fileSegments.findIndex( + (segment) => segment === lastTargetSegment + ) + + if (commonDirIndex === -1) { + // Return just the filename if no common directory is found + return fileSegments[fileSegments.length - 1] + } + + // Return everything after the common directory + return fileSegments.slice(commonDirIndex + 1).join("/") +} diff --git a/packages/shadcn/test/utils/updaters/update-files.test.ts b/packages/shadcn/test/utils/updaters/update-files.test.ts index 4d12fcb841e..c605c357896 100644 --- a/packages/shadcn/test/utils/updaters/update-files.test.ts +++ b/packages/shadcn/test/utils/updaters/update-files.test.ts @@ -1,65 +1,471 @@ import { describe, expect, test } from "vitest" -import { resolveTargetDir } from "../../../src/utils/updaters/update-files" +import { + findCommonRoot, + resolveFilePath, + resolveNestedFilePath, +} from "../../../src/utils/updaters/update-files" -describe("resolveTargetDir", () => { - test("should handle a home target without a src directory", () => { - const targetDir = resolveTargetDir( - { +describe("resolveFilePath", () => { + test.each([ + { + description: "should use target when provided", + file: { + path: "hello-world/ui/button.tsx", + type: "registry:ui", + target: "ui/button.tsx", + }, + resolvedPath: "/foo/bar/ui/button.tsx", + projectInfo: { isSrcDir: false, }, - { - resolvedPaths: { - cwd: "/foo/bar", - }, + }, + { + description: "should use nested target when provided", + file: { + path: "hello-world/components/example-card.tsx", + type: "registry:component", + target: "components/cards/example-card.tsx", + }, + resolvedPath: "/foo/bar/components/cards/example-card.tsx", + projectInfo: { + isSrcDir: false, + }, + }, + { + description: "should use home target (~) when provided", + file: { + path: "hello-world/foo.json", + type: "registry:lib", + target: "~/foo.json", }, - "~/.env" - ) - expect(targetDir).toBe("/foo/bar/.env") + resolvedPath: "/foo/bar/foo.json", + projectInfo: { + isSrcDir: false, + }, + }, + ])("$description", ({ file, resolvedPath, projectInfo }) => { + expect( + resolveFilePath( + file, + { + resolvedPaths: { + cwd: "/foo/bar", + components: "/foo/bar/components", + ui: "/foo/bar/components/ui", + lib: "/foo/bar/lib", + hooks: "/foo/bar/hooks", + }, + }, + projectInfo + ) + ).toBe(resolvedPath) }) - test("should handle a home target even with a src directory", () => { - const targetDir = resolveTargetDir( - { + test.each([ + { + description: "should use src directory when provided", + file: { + path: "hello-world/ui/button.tsx", + type: "registry:ui", + target: "design-system/ui/button.tsx", + }, + resolvedPath: "/foo/bar/src/design-system/ui/button.tsx", + projectInfo: { isSrcDir: true, }, - { - resolvedPaths: { - cwd: "/foo/bar", - }, + }, + { + description: "should NOT use src directory for root files", + file: { + path: "hello-world/.env", + type: "registry:lib", + target: "~/.env", }, - "~/.env" - ) - expect(targetDir).toBe("/foo/bar/.env") - }) - - test("should handle a simple target", () => { - const targetDir = resolveTargetDir( - { + resolvedPath: "/foo/bar/.env", + projectInfo: { + isSrcDir: true, + }, + }, + { + description: "should use src directory when isSrcDir is true", + file: { + path: "hello-world/lib/foo.ts", + type: "registry:lib", + target: "lib/foo.ts", + }, + resolvedPath: "/foo/bar/src/lib/foo.ts", + projectInfo: { + isSrcDir: true, + }, + }, + { + description: "should strip src directory when isSrcDir is false", + file: { + path: "hello-world/path/to/bar/baz.ts", + type: "registry:lib", + target: "src/path/to/bar/baz.ts", + }, + resolvedPath: "/foo/bar/path/to/bar/baz.ts", + projectInfo: { isSrcDir: false, }, - { - resolvedPaths: { - cwd: "/foo/bar", + }, + ])("$description", ({ file, resolvedPath, projectInfo }) => { + expect( + resolveFilePath( + file, + { + resolvedPaths: { + cwd: "/foo/bar", + components: "/foo/bar/src/components", + ui: "/foo/bar/src/primitives", + lib: "/foo/bar/src/lib", + hooks: "/foo/bar/src/hooks", + }, }, - }, - "./components/ui/button.tsx" - ) - expect(targetDir).toBe("/foo/bar/components/ui/button.tsx") + projectInfo + ) + ).toBe(resolvedPath) }) - test("should handle a simple target with src directory", () => { - const targetDir = resolveTargetDir( - { - isSrcDir: true, - }, - { - resolvedPaths: { - cwd: "/foo/bar", + test("should resolve registry:ui file types", () => { + expect( + resolveFilePath( + { + path: "hello-world/ui/button.tsx", + type: "registry:ui", }, - }, - "./components/ui/button.tsx" - ) - expect(targetDir).toBe("/foo/bar/src/components/ui/button.tsx") + { + resolvedPaths: { + cwd: "/foo/bar", + components: "/foo/bar/components", + ui: "/foo/bar/components/ui", + lib: "/foo/bar/lib", + hooks: "/foo/bar/hooks", + }, + }, + { + isSrcDir: false, + } + ) + ).toBe("/foo/bar/components/ui/button.tsx") + + expect( + resolveFilePath( + { + path: "hello-world/ui/button.tsx", + type: "registry:ui", + }, + { + resolvedPaths: { + cwd: "/foo/bar", + components: "/foo/bar/src/components", + ui: "/foo/bar/src/primitives", + lib: "/foo/bar/src/lib", + hooks: "/foo/bar/src/hooks", + }, + }, + { + isSrcDir: true, + } + ) + ).toBe("/foo/bar/src/primitives/button.tsx") + }) + + test("should resolve registry:component and registry:block file types", () => { + expect( + resolveFilePath( + { + path: "hello-world/components/example-card.tsx", + type: "registry:component", + }, + { + resolvedPaths: { + cwd: "/foo/bar", + components: "/foo/bar/components", + ui: "/foo/bar/components/ui", + lib: "/foo/bar/lib", + hooks: "/foo/bar/hooks", + }, + }, + { + isSrcDir: false, + } + ) + ).toBe("/foo/bar/components/example-card.tsx") + + expect( + resolveFilePath( + { + path: "hello-world/components/example-card.tsx", + type: "registry:block", + }, + { + resolvedPaths: { + cwd: "/foo/bar", + components: "/foo/bar/components", + ui: "/foo/bar/components/ui", + lib: "/foo/bar/lib", + hooks: "/foo/bar/hooks", + }, + }, + { + isSrcDir: false, + } + ) + ).toBe("/foo/bar/components/example-card.tsx") + + expect( + resolveFilePath( + { + path: "hello-world/components/example-card.tsx", + type: "registry:component", + }, + { + resolvedPaths: { + cwd: "/foo/bar", + components: "/foo/bar/src/components", + ui: "/foo/bar/src/primitives", + lib: "/foo/bar/src/lib", + hooks: "/foo/bar/src/hooks", + }, + }, + { + isSrcDir: true, + } + ) + ).toBe("/foo/bar/src/components/example-card.tsx") + + expect( + resolveFilePath( + { + path: "hello-world/components/example-card.tsx", + type: "registry:block", + }, + { + resolvedPaths: { + cwd: "/foo/bar", + components: "/foo/bar/src/components", + ui: "/foo/bar/src/primitives", + lib: "/foo/bar/src/lib", + hooks: "/foo/bar/src/hooks", + }, + }, + { + isSrcDir: true, + } + ) + ).toBe("/foo/bar/src/components/example-card.tsx") + }) + + test("should resolve registry:lib file types", () => { + expect( + resolveFilePath( + { + path: "hello-world/lib/foo.ts", + type: "registry:lib", + }, + { + resolvedPaths: { + cwd: "/foo/bar", + components: "/foo/bar/components", + ui: "/foo/bar/components/ui", + lib: "/foo/bar/lib", + hooks: "/foo/bar/hooks", + }, + }, + { + isSrcDir: false, + } + ) + ).toBe("/foo/bar/lib/foo.ts") + + expect( + resolveFilePath( + { + path: "hello-world/lib/foo.ts", + type: "registry:lib", + }, + { + resolvedPaths: { + cwd: "/foo/bar", + components: "/foo/bar/src/components", + ui: "/foo/bar/src/primitives", + lib: "/foo/bar/src/lib", + hooks: "/foo/bar/src/hooks", + }, + }, + { + isSrcDir: true, + } + ) + ).toBe("/foo/bar/src/lib/foo.ts") + }) + + test("should resolve registry:hook file types", () => { + expect( + resolveFilePath( + { + path: "hello-world/hooks/use-foo.ts", + type: "registry:hook", + }, + { + resolvedPaths: { + cwd: "/foo/bar", + components: "/foo/bar/components", + ui: "/foo/bar/components/ui", + lib: "/foo/bar/lib", + hooks: "/foo/bar/hooks", + }, + }, + { + isSrcDir: false, + } + ) + ).toBe("/foo/bar/hooks/use-foo.ts") + + expect( + resolveFilePath( + { + path: "hello-world/hooks/use-foo.ts", + type: "registry:hook", + }, + { + resolvedPaths: { + cwd: "/foo/bar", + components: "/foo/bar/src/components", + ui: "/foo/bar/src/primitives", + lib: "/foo/bar/src/lib", + hooks: "/foo/bar/src/hooks", + }, + }, + { + isSrcDir: true, + } + ) + ).toBe("/foo/bar/src/hooks/use-foo.ts") + }) + + test("should resolve nested files", () => { + expect( + resolveFilePath( + { + path: "hello-world/components/path/to/example-card.tsx", + type: "registry:component", + }, + { + resolvedPaths: { + cwd: "/foo/bar", + components: "/foo/bar/components", + ui: "/foo/bar/components/ui", + lib: "/foo/bar/lib", + hooks: "/foo/bar/hooks", + }, + }, + { + isSrcDir: false, + } + ) + ).toBe("/foo/bar/components/path/to/example-card.tsx") + + expect( + resolveFilePath( + { + path: "hello-world/design-system/primitives/button.tsx", + type: "registry:ui", + }, + { + resolvedPaths: { + cwd: "/foo/bar", + components: "/foo/bar/components", + ui: "/foo/bar/components/ui", + lib: "/foo/bar/lib", + hooks: "/foo/bar/hooks", + }, + }, + { + isSrcDir: false, + } + ) + ).toBe("/foo/bar/components/ui/button.tsx") + }) +}) + +describe("findCommonRoot", () => { + test.each([ + { + description: "should find the common root of sibling files", + paths: ["/foo/bar/baz/qux", "/foo/bar/baz/quux"], + needle: "/foo/bar/baz/qux", + expected: "/foo/bar/baz", + }, + { + description: "should find common root with nested structures", + paths: [ + "/app/components/header/nav.tsx", + "/app/components/header/logo.tsx", + "/app/components/header/menu/item.tsx", + ], + needle: "/app/components/header/nav.tsx", + expected: "/app/components/header", + }, + { + description: "should handle single file in paths", + paths: ["/foo/bar/baz/single.tsx"], + needle: "/foo/bar/baz/single.tsx", + expected: "/foo/bar/baz", + }, + { + description: "should handle root level files", + paths: ["root.tsx", "config.ts", "package.json"], + needle: "root.tsx", + expected: "", + }, + { + description: "should handle unrelated paths", + paths: ["/foo/bar/baz", "/completely/different/path"], + needle: "/foo/bar/baz", + expected: "/foo/bar", + }, + ])("$description", ({ paths, needle, expected }) => { + expect(findCommonRoot(paths, needle)).toBe(expected) + }) +}) + +describe("resolveNestedFilePath", () => { + test.each([ + { + description: "should resolve path after common components directory", + filePath: "hello-world/components/path/to/example-card.tsx", + targetDir: "/foo/bar/components", + expected: "path/to/example-card.tsx", + }, + { + description: "should handle different directory depths", + filePath: "/foo-bar/components/ui/button.tsx", + targetDir: "/src/ui", + expected: "button.tsx", + }, + { + description: "should handle nested component paths", + filePath: "blocks/sidebar/components/nav/item.tsx", + targetDir: "/app/components", + expected: "nav/item.tsx", + }, + { + description: "should return the file path if no common directory", + filePath: "something/else/file.tsx", + targetDir: "/foo/bar/components", + expected: "file.tsx", + }, + { + description: "should handle paths with multiple common directories", + filePath: "ui/shared/components/utils/button.tsx", + targetDir: "/src/components/utils", + expected: "button.tsx", + }, + ])("$description", ({ filePath, targetDir, expected }) => { + expect(resolveNestedFilePath(filePath, targetDir)).toBe(expected) }) }) From 5f7957ab51c3d929f322937147269ef3bdfeb472 Mon Sep 17 00:00:00 2001 From: shadcn Date: Thu, 16 Jan 2025 15:46:33 +0400 Subject: [PATCH 2/2] feat(shadcn): add new registry:file type (#6377) * feat(shadcn): add new registry:file type * chore: add changeset * fix: file target --- .changeset/swift-eyes-dress.md | 5 ++++ apps/www/lib/registry.ts | 2 +- apps/www/public/registry.json | 3 ++- apps/www/public/schema/registry-item.json | 20 +++++++++++++--- packages/shadcn/src/registry/schema.ts | 24 +++++++++++++------ .../test/utils/updaters/update-files.test.ts | 22 ++++++++++++++++- 6 files changed, 63 insertions(+), 13 deletions(-) create mode 100644 .changeset/swift-eyes-dress.md diff --git a/.changeset/swift-eyes-dress.md b/.changeset/swift-eyes-dress.md new file mode 100644 index 00000000000..e4d09d95407 --- /dev/null +++ b/.changeset/swift-eyes-dress.md @@ -0,0 +1,5 @@ +--- +"shadcn": minor +--- + +new registry:file type diff --git a/apps/www/lib/registry.ts b/apps/www/lib/registry.ts index 390f87e688f..ba1243d1189 100644 --- a/apps/www/lib/registry.ts +++ b/apps/www/lib/registry.ts @@ -157,7 +157,7 @@ function getFileTarget(file: z.infer) { } } - return target + return target ?? "" } async function createTempSourceFile(filename: string) { diff --git a/apps/www/public/registry.json b/apps/www/public/registry.json index d5aab0a9882..ce32326b04e 100644 --- a/apps/www/public/registry.json +++ b/apps/www/public/registry.json @@ -14,7 +14,8 @@ "registry:ui", "registry:hook", "registry:theme", - "registry:page" + "registry:page", + "registry:file" ] }, "description": { diff --git a/apps/www/public/schema/registry-item.json b/apps/www/public/schema/registry-item.json index ee8c10b9148..8d4fd3e241c 100644 --- a/apps/www/public/schema/registry-item.json +++ b/apps/www/public/schema/registry-item.json @@ -14,7 +14,8 @@ "registry:ui", "registry:hook", "registry:theme", - "registry:page" + "registry:page", + "registry:file" ] }, "description": { @@ -64,14 +65,27 @@ "registry:ui", "registry:hook", "registry:theme", - "registry:page" + "registry:page", + "registry:file" ] }, "target": { "type": "string" } }, - "required": ["path", "type"] + "if": { + "properties": { + "type": { + "enum": ["registry:file", "registry:page"] + } + } + }, + "then": { + "required": ["path", "type", "target"] + }, + "else": { + "required": ["path", "type"] + } } }, "tailwind": { diff --git a/packages/shadcn/src/registry/schema.ts b/packages/shadcn/src/registry/schema.ts index c6ad858c134..e4a7cac21fa 100644 --- a/packages/shadcn/src/registry/schema.ts +++ b/packages/shadcn/src/registry/schema.ts @@ -9,21 +9,31 @@ export const registryItemTypeSchema = z.enum([ "registry:component", "registry:ui", "registry:hook", - "registry:theme", "registry:page", + "registry:file", // Internal use only + "registry:theme", "registry:example", "registry:style", "registry:internal", ]) -export const registryItemFileSchema = z.object({ - path: z.string(), - content: z.string().optional(), - type: registryItemTypeSchema, - target: z.string().optional(), -}) +export const registryItemFileSchema = z.discriminatedUnion("type", [ + // Target is required for registry:file and registry:page + z.object({ + path: z.string(), + content: z.string().optional(), + type: z.enum(["registry:file", "registry:page"]), + target: z.string(), + }), + z.object({ + path: z.string(), + content: z.string().optional(), + type: registryItemTypeSchema.exclude(["registry:file", "registry:page"]), + target: z.string().optional(), + }), +]) export const registryItemTailwindSchema = z.object({ config: z diff --git a/packages/shadcn/test/utils/updaters/update-files.test.ts b/packages/shadcn/test/utils/updaters/update-files.test.ts index c605c357896..d0f76c8bdc4 100644 --- a/packages/shadcn/test/utils/updaters/update-files.test.ts +++ b/packages/shadcn/test/utils/updaters/update-files.test.ts @@ -79,7 +79,7 @@ describe("resolveFilePath", () => { description: "should NOT use src directory for root files", file: { path: "hello-world/.env", - type: "registry:lib", + type: "registry:file", target: "~/.env", }, resolvedPath: "/foo/bar/.env", @@ -347,6 +347,26 @@ describe("resolveFilePath", () => { ).toBe("/foo/bar/src/hooks/use-foo.ts") }) + test("should resolve registry:file file types", () => { + expect( + resolveFilePath( + { + path: "hello-world/.env", + type: "registry:file", + target: "~/baz/.env", + }, + { + resolvedPaths: { + cwd: "/foo/bar", + }, + }, + { + isSrcDir: false, + } + ) + ).toBe("/foo/bar/baz/.env") + }) + test("should resolve nested files", () => { expect( resolveFilePath(