diff --git a/.changeset/hip-moose-add.md b/.changeset/hip-moose-add.md new file mode 100644 index 00000000000..de869d44ad0 --- /dev/null +++ b/.changeset/hip-moose-add.md @@ -0,0 +1,5 @@ +--- +"shadcn": minor +--- + +recursively resolve registry dependencies diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml index f354abc95a2..1f1932756aa 100644 --- a/.github/workflows/prerelease.yml +++ b/.github/workflows/prerelease.yml @@ -54,7 +54,7 @@ jobs: path: packages/shadcn - name: Upload packaged artifact - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: npm-package-shadcn@${{ steps.package-version.outputs.current-version }}-pr-${{ github.event.number }} # encode the PR number into the artifact name path: packages/shadcn/dist/index.js diff --git a/packages/shadcn/src/utils/registry/index.ts b/packages/shadcn/src/utils/registry/index.ts index b70b1506235..429ce4c4add 100644 --- a/packages/shadcn/src/utils/registry/index.ts +++ b/packages/shadcn/src/utils/registry/index.ts @@ -265,44 +265,33 @@ export async function registryResolveItemsTree( return null } - let items = ( - await Promise.all( - names.map(async (name) => { - const item = await getRegistryItem(name, config.style) - return item - }) - ) - ).filter((item): item is NonNullable => item !== null) - - if (!items.length) { - return null + // If we're resolving the index, we want it to go first. + if (names.includes("index")) { + names.unshift("index") } - const registryDependencies: string[] = items - .map((item) => item.registryDependencies ?? []) - .flat() + let registryDependencies: string[] = [] + for (const name of names) { + const itemRegistryDependencies = await resolveRegistryDependencies( + name, + config + ) + registryDependencies.push(...itemRegistryDependencies) + } - const uniqueDependencies = Array.from(new Set(registryDependencies)) - const urls = Array.from([...names, ...uniqueDependencies]).map((name) => - getRegistryUrl(isUrl(name) ? name : `styles/${config.style}/${name}.json`) - ) - let result = await fetchRegistry(urls) + const uniqueRegistryDependencies = Array.from(new Set(registryDependencies)) + let result = await fetchRegistry(uniqueRegistryDependencies) const payload = z.array(registryItemSchema).parse(result) if (!payload) { return null } - // If we're resolving the index, we want it to go first. + // If we're resolving the index, we want to fetch + // the theme item if a base color is provided. + // We do this for index only. + // Other components will ship with their theme tokens. if (names.includes("index")) { - const index = await getRegistryItem("index", config.style) - if (index) { - payload.unshift(index) - } - - // Fetch the theme item if a base color is provided. - // We do this for index only. - // Other components will ship with their theme tokens. if (config.tailwind.baseColor) { const theme = await registryGetTheme(config.tailwind.baseColor, config) if (theme) { @@ -346,6 +335,46 @@ export async function registryResolveItemsTree( } } +async function resolveRegistryDependencies( + url: string, + config: Config +): Promise { + const visited = new Set() + const payload: string[] = [] + + async function resolveDependencies(itemUrl: string) { + const url = getRegistryUrl( + isUrl(itemUrl) ? itemUrl : `styles/${config.style}/${itemUrl}.json` + ) + + if (visited.has(url)) { + return + } + + visited.add(url) + + try { + const [result] = await fetchRegistry([url]) + const item = registryItemSchema.parse(result) + payload.push(url) + + if (item.registryDependencies) { + for (const dependency of item.registryDependencies) { + await resolveDependencies(dependency) + } + } + } catch (error) { + console.error( + `Error fetching or parsing registry item at ${itemUrl}:`, + error + ) + } + } + + await resolveDependencies(url) + return Array.from(new Set(payload)) +} + export async function registryGetTheme(name: string, config: Config) { const baseColor = await getRegistryBaseColor(name) if (!baseColor) { diff --git a/packages/shadcn/test/utils/schema/__snapshots__/registry-resolve-items-tree.test.ts.snap b/packages/shadcn/test/utils/schema/__snapshots__/registry-resolve-items-tree.test.ts.snap index 9bdcd791efa..02feee5efe3 100644 --- a/packages/shadcn/test/utils/schema/__snapshots__/registry-resolve-items-tree.test.ts.snap +++ b/packages/shadcn/test/utils/schema/__snapshots__/registry-resolve-items-tree.test.ts.snap @@ -12,16 +12,24 @@ exports[`registryResolveItemTree > should resolve index 1`] = ` "tailwindcss-animate", "class-variance-authority", "lucide-react", - "tailwindcss-animate", - "class-variance-authority", - "lucide-react", - "@radix-ui/react-label", "clsx", "tailwind-merge", + "@radix-ui/react-label", ], "devDependencies": [], "docs": "", "files": [ + { + "content": "import { clsx, type ClassValue } from "clsx" +import { twMerge } from "tailwind-merge" + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)) +} +", + "path": "lib/utils.ts", + "type": "registry:lib", + }, { "content": ""use client" @@ -54,23 +62,11 @@ export { Label } "target": "", "type": "registry:ui", }, - { - "content": "import { clsx, type ClassValue } from "clsx" -import { twMerge } from "tailwind-merge" - -export function cn(...inputs: ClassValue[]) { - return twMerge(clsx(inputs)) -} -", - "path": "lib/utils.ts", - "type": "registry:lib", - }, ], "tailwind": { "config": { "plugins": [ "require("tailwindcss-animate")", - "require("tailwindcss-animate")", ], "theme": { "extend": {