Skip to content

Commit 28f34ed

Browse files
authored
feat(shadcn): recursively resolve registry dependencies (#4961)
* feat(shadcn): recursively resolve registry dependencies * chore: add changeset * ci: update actions/upload-artifact
1 parent bd54184 commit 28f34ed

4 files changed

Lines changed: 75 additions & 45 deletions

File tree

.changeset/hip-moose-add.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"shadcn": minor
3+
---
4+
5+
recursively resolve registry dependencies

.github/workflows/prerelease.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ jobs:
5454
path: packages/shadcn
5555

5656
- name: Upload packaged artifact
57-
uses: actions/upload-artifact@v2
57+
uses: actions/upload-artifact@v3
5858
with:
5959
name: npm-package-shadcn@${{ steps.package-version.outputs.current-version }}-pr-${{ github.event.number }} # encode the PR number into the artifact name
6060
path: packages/shadcn/dist/index.js

packages/shadcn/src/utils/registry/index.ts

Lines changed: 57 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -265,44 +265,33 @@ export async function registryResolveItemsTree(
265265
return null
266266
}
267267

268-
let items = (
269-
await Promise.all(
270-
names.map(async (name) => {
271-
const item = await getRegistryItem(name, config.style)
272-
return item
273-
})
274-
)
275-
).filter((item): item is NonNullable<typeof item> => item !== null)
276-
277-
if (!items.length) {
278-
return null
268+
// If we're resolving the index, we want it to go first.
269+
if (names.includes("index")) {
270+
names.unshift("index")
279271
}
280272

281-
const registryDependencies: string[] = items
282-
.map((item) => item.registryDependencies ?? [])
283-
.flat()
273+
let registryDependencies: string[] = []
274+
for (const name of names) {
275+
const itemRegistryDependencies = await resolveRegistryDependencies(
276+
name,
277+
config
278+
)
279+
registryDependencies.push(...itemRegistryDependencies)
280+
}
284281

285-
const uniqueDependencies = Array.from(new Set(registryDependencies))
286-
const urls = Array.from([...names, ...uniqueDependencies]).map((name) =>
287-
getRegistryUrl(isUrl(name) ? name : `styles/${config.style}/${name}.json`)
288-
)
289-
let result = await fetchRegistry(urls)
282+
const uniqueRegistryDependencies = Array.from(new Set(registryDependencies))
283+
let result = await fetchRegistry(uniqueRegistryDependencies)
290284
const payload = z.array(registryItemSchema).parse(result)
291285

292286
if (!payload) {
293287
return null
294288
}
295289

296-
// If we're resolving the index, we want it to go first.
290+
// If we're resolving the index, we want to fetch
291+
// the theme item if a base color is provided.
292+
// We do this for index only.
293+
// Other components will ship with their theme tokens.
297294
if (names.includes("index")) {
298-
const index = await getRegistryItem("index", config.style)
299-
if (index) {
300-
payload.unshift(index)
301-
}
302-
303-
// Fetch the theme item if a base color is provided.
304-
// We do this for index only.
305-
// Other components will ship with their theme tokens.
306295
if (config.tailwind.baseColor) {
307296
const theme = await registryGetTheme(config.tailwind.baseColor, config)
308297
if (theme) {
@@ -346,6 +335,46 @@ export async function registryResolveItemsTree(
346335
}
347336
}
348337

338+
async function resolveRegistryDependencies(
339+
url: string,
340+
config: Config
341+
): Promise<string[]> {
342+
const visited = new Set<string>()
343+
const payload: string[] = []
344+
345+
async function resolveDependencies(itemUrl: string) {
346+
const url = getRegistryUrl(
347+
isUrl(itemUrl) ? itemUrl : `styles/${config.style}/${itemUrl}.json`
348+
)
349+
350+
if (visited.has(url)) {
351+
return
352+
}
353+
354+
visited.add(url)
355+
356+
try {
357+
const [result] = await fetchRegistry([url])
358+
const item = registryItemSchema.parse(result)
359+
payload.push(url)
360+
361+
if (item.registryDependencies) {
362+
for (const dependency of item.registryDependencies) {
363+
await resolveDependencies(dependency)
364+
}
365+
}
366+
} catch (error) {
367+
console.error(
368+
`Error fetching or parsing registry item at ${itemUrl}:`,
369+
error
370+
)
371+
}
372+
}
373+
374+
await resolveDependencies(url)
375+
return Array.from(new Set(payload))
376+
}
377+
349378
export async function registryGetTheme(name: string, config: Config) {
350379
const baseColor = await getRegistryBaseColor(name)
351380
if (!baseColor) {

packages/shadcn/test/utils/schema/__snapshots__/registry-resolve-items-tree.test.ts.snap

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,24 @@ exports[`registryResolveItemTree > should resolve index 1`] = `
1212
"tailwindcss-animate",
1313
"class-variance-authority",
1414
"lucide-react",
15-
"tailwindcss-animate",
16-
"class-variance-authority",
17-
"lucide-react",
18-
"@radix-ui/react-label",
1915
"clsx",
2016
"tailwind-merge",
17+
"@radix-ui/react-label",
2118
],
2219
"devDependencies": [],
2320
"docs": "",
2421
"files": [
22+
{
23+
"content": "import { clsx, type ClassValue } from "clsx"
24+
import { twMerge } from "tailwind-merge"
25+
26+
export function cn(...inputs: ClassValue[]) {
27+
return twMerge(clsx(inputs))
28+
}
29+
",
30+
"path": "lib/utils.ts",
31+
"type": "registry:lib",
32+
},
2533
{
2634
"content": ""use client"
2735
@@ -54,23 +62,11 @@ export { Label }
5462
"target": "",
5563
"type": "registry:ui",
5664
},
57-
{
58-
"content": "import { clsx, type ClassValue } from "clsx"
59-
import { twMerge } from "tailwind-merge"
60-
61-
export function cn(...inputs: ClassValue[]) {
62-
return twMerge(clsx(inputs))
63-
}
64-
",
65-
"path": "lib/utils.ts",
66-
"type": "registry:lib",
67-
},
6865
],
6966
"tailwind": {
7067
"config": {
7168
"plugins": [
7269
"require("tailwindcss-animate")",
73-
"require("tailwindcss-animate")",
7470
],
7571
"theme": {
7672
"extend": {

0 commit comments

Comments
 (0)