11import fs from "node:fs/promises"
2- import { tmpdir } from "os "
3- import path from "path "
4- import { RegistryItem , registryItemSchema } from "shadcn/schema"
5- import { Project , ScriptKind } from "ts-morph "
2+ import path from "node:path "
3+ import { cache } from "react "
4+ import { registryItemSchema } from "shadcn/schema"
5+ import type { RegistryItem } from "shadcn/schema "
66
77import { Index } from "@/registry/__index__"
88
99// Fumadocs zod v4 compat type fix. Temporary.
1010interface RegistryItemFile {
1111 path : string
12- content : string
13- type : RegistryItem [ "type" ]
14- target : string
12+ type ?: RegistryItem [ "type" ]
13+ target ?: string
1514}
1615
1716export function getRegistryComponent ( name : string ) {
1817 return Index [ name ] ?. component
1918}
2019
21- export async function getRegistryItem ( name : string ) {
20+ interface RegistryItemFileWithContent extends RegistryItemFile {
21+ content : string
22+ }
23+
24+ const registryImportPattern =
25+ / @ \/ ( .+ ?) \/ ( (?: .* ?\/ ) ? (?: c o m p o n e n t s | u i | h o o k s | l i b ) ) \/ ( [ \w - ] + ) / g
26+
27+ const getCachedFileContent = cache (
28+ async ( filePath : string , fileType : RegistryItem [ "type" ] | undefined ) => {
29+ let code = await fs . readFile ( filePath , "utf-8" )
30+
31+ // Some registry items use default exports, but published snippets should not.
32+ if ( fileType !== "registry:page" ) {
33+ code = code . replaceAll ( "export default" , "export" )
34+ }
35+
36+ return fixImport ( code )
37+ }
38+ )
39+
40+ const getCachedRegistryItem = cache ( async ( name : string ) => {
2241 const item = Index [ name ]
2342
2443 if ( ! item ) {
2544 return null
2645 }
2746
28- // Convert all file paths to object.
29- // TODO: remove when we migrate to new registry.
30- item . files = item . files . map ( ( file : unknown ) =>
31- typeof file === "string" ? { path : file } : file
32- )
47+ const normalizedItem = {
48+ ...item ,
49+ files : normalizeRegistryItemFiles ( item . files ) ,
50+ }
3351
3452 // Fail early before doing expensive file operations.
35- const result = registryItemSchema . safeParse ( item )
53+ const result = registryItemSchema . safeParse ( normalizedItem )
3654 if ( ! result . success ) {
3755 return null
3856 }
3957
40- let files : typeof result . data . files = [ ]
41- for ( const file of item . files ) {
42- const content = await getFileContent ( file )
43- const relativePath = path . relative ( process . cwd ( ) , file . path )
44-
45- files . push ( {
46- ...file ,
47- path : relativePath ,
48- content,
49- } )
50- }
51-
52- // Fix file paths.
53- files = fixFilePaths ( files as RegistryItemFile [ ] )
58+ const files = fixFilePaths (
59+ await Promise . all (
60+ ( result . data . files ?? [ ] ) . map ( async ( file ) => {
61+ const content = await getCachedFileContent ( file . path , file . type )
62+ return {
63+ ...file ,
64+ path : path . relative ( process . cwd ( ) , file . path ) ,
65+ content,
66+ }
67+ } )
68+ )
69+ )
5470
5571 const parsed = registryItemSchema . safeParse ( {
5672 ...result . data ,
@@ -63,45 +79,31 @@ export async function getRegistryItem(name: string) {
6379 }
6480
6581 return parsed . data
66- }
67-
68- async function getFileContent ( file : RegistryItemFile ) {
69- const raw = await fs . readFile ( file . path , "utf-8" )
70-
71- const project = new Project ( {
72- compilerOptions : { } ,
73- } )
74-
75- const tempFile = await createTempSourceFile ( file . path )
76- const sourceFile = project . createSourceFile ( tempFile , raw , {
77- scriptKind : ScriptKind . TSX ,
78- } )
79-
80- // Remove meta variables.
81- // removeVariable(sourceFile, "iframeHeight")
82- // removeVariable(sourceFile, "containerClassName")
83- // removeVariable(sourceFile, "description")
82+ } )
8483
85- let code = sourceFile . getFullText ( )
84+ export async function getRegistryItem ( name : string ) {
85+ return getCachedRegistryItem ( name )
86+ }
8687
87- // Some registry items uses default export.
88- // We want to use named export instead.
89- // TODO: do we really need this? - @shadcn.
90- if ( file . type !== "registry:page" ) {
91- code = code . replaceAll ( "export default" , "export" )
88+ function normalizeRegistryItemFiles ( files : unknown ) {
89+ if ( ! Array . isArray ( files ) ) {
90+ return files
9291 }
9392
94- // Fix imports.
95- code = fixImport ( code )
93+ return files . map ( ( file ) => {
94+ if ( typeof file === "string" ) {
95+ return { path : file }
96+ }
9697
97- return code
98+ return file
99+ } )
98100}
99101
100102function getFileTarget ( file : RegistryItemFile ) {
101103 let target = file . target
102104
103105 if ( ! target || target === "" ) {
104- const fileName = file . path . split ( "/" ) . pop ( )
106+ const fileName = path . basename ( file . path )
105107 if (
106108 file . type === "registry:block" ||
107109 file . type === "registry:component" ||
@@ -126,13 +128,8 @@ function getFileTarget(file: RegistryItemFile) {
126128 return target ?? ""
127129}
128130
129- async function createTempSourceFile ( filename : string ) {
130- const dir = await fs . mkdtemp ( path . join ( tmpdir ( ) , "shadcn-" ) )
131- return path . join ( dir , filename )
132- }
133-
134- function fixFilePaths ( files : RegistryItemFile [ ] ) {
135- if ( ! files ) {
131+ function fixFilePaths ( files : RegistryItemFileWithContent [ ] ) {
132+ if ( files . length === 0 ) {
136133 return [ ]
137134 }
138135
@@ -150,11 +147,9 @@ function fixFilePaths(files: RegistryItemFile[]) {
150147}
151148
152149export function fixImport ( content : string ) {
153- const regex = / @ \/ ( .+ ?) \/ ( (?: .* ?\/ ) ? (?: c o m p o n e n t s | u i | h o o k s | l i b ) ) \/ ( [ \w - ] + ) / g
154-
155150 const replacement = (
156151 match : string ,
157- path : string ,
152+ _sourcePath : string ,
158153 type : string ,
159154 component : string
160155 ) => {
@@ -171,7 +166,7 @@ export function fixImport(content: string) {
171166 return match
172167 }
173168
174- return content . replace ( regex , replacement )
169+ return content . replace ( registryImportPattern , replacement )
175170}
176171
177172export type FileTree = {
0 commit comments