1+ import { existsSync } from "fs"
2+ import path from "path"
13import { getConfig } from "@/src/utils/get-config"
2- import { getProjectInfo } from "@/src/utils/get-project-info"
4+ import {
5+ formatMonorepoMessage ,
6+ getMonorepoTargets ,
7+ isMonorepoRoot ,
8+ } from "@/src/utils/get-monorepo-info"
9+ import {
10+ getProjectComponents ,
11+ getProjectInfo ,
12+ type ProjectInfo ,
13+ } from "@/src/utils/get-project-info"
314import { handleError } from "@/src/utils/handle-error"
15+ import { highlighter } from "@/src/utils/highlighter"
416import { logger } from "@/src/utils/logger"
517import { Command } from "commander"
618
19+ const GITHUB_RAW_BASE =
20+ "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases"
21+
722export const info = new Command ( )
823 . name ( "info" )
924 . description ( "get information about your project" )
@@ -12,14 +27,224 @@ export const info = new Command()
1227 "the working directory. defaults to the current directory." ,
1328 process . cwd ( )
1429 )
30+ . option ( "--json" , "output as JSON." , false )
1531 . action ( async ( opts ) => {
1632 try {
17- logger . info ( "> project info" )
18- console . log ( await getProjectInfo ( opts . cwd ) )
19- logger . break ( )
20- logger . info ( "> components.json" )
21- console . log ( await getConfig ( opts . cwd ) )
33+ const cwd = path . resolve ( opts . cwd )
34+
35+ // Check if we're in a monorepo root.
36+ if (
37+ ! existsSync ( path . resolve ( cwd , "components.json" ) ) &&
38+ ( await isMonorepoRoot ( cwd ) )
39+ ) {
40+ const targets = await getMonorepoTargets ( cwd )
41+ if ( targets . length > 0 ) {
42+ if ( opts . json ) {
43+ console . log (
44+ JSON . stringify (
45+ {
46+ error : "monorepo_root" ,
47+ message :
48+ "You are running info from a monorepo root. Use the -c flag to specify a workspace." ,
49+ targets : targets . map ( ( t ) => t . name ) ,
50+ } ,
51+ null ,
52+ 2
53+ )
54+ )
55+ } else {
56+ formatMonorepoMessage ( "info" , targets )
57+ }
58+ process . exit ( 1 )
59+ }
60+ }
61+
62+ const projectInfo = await getProjectInfo ( cwd )
63+ const config = await getConfig ( cwd )
64+ const components = await getProjectComponents ( cwd )
65+ const base = getBase ( config ?. style )
66+ const data = collectInfo ( projectInfo , config , components , base )
67+
68+ if ( opts . json ) {
69+ console . log ( JSON . stringify ( data , null , 2 ) )
70+ return
71+ }
72+
73+ printInfo ( data )
2274 } catch ( error ) {
2375 handleError ( error )
2476 }
2577 } )
78+
79+ function getBase ( style : string | undefined ) {
80+ return style ?. startsWith ( "base-" ) ? "base" : "radix"
81+ }
82+
83+ function getRegistries (
84+ registries : Record < string , string | { url : string } > | undefined
85+ ) {
86+ if ( ! registries ) {
87+ return { }
88+ }
89+
90+ const result : Record < string , string > = { }
91+ for ( const [ name , value ] of Object . entries ( registries ) ) {
92+ result [ name ] = typeof value === "string" ? value : value . url
93+ }
94+ return result
95+ }
96+
97+ function collectInfo (
98+ projectInfo : ProjectInfo | null ,
99+ config : Awaited < ReturnType < typeof getConfig > > ,
100+ components : string [ ] ,
101+ base : string
102+ ) {
103+ return {
104+ project : projectInfo
105+ ? {
106+ framework : projectInfo . framework . label ,
107+ frameworkName : projectInfo . framework . name ,
108+ frameworkVersion : projectInfo . frameworkVersion ?? null ,
109+ srcDirectory : projectInfo . isSrcDir ,
110+ rsc : projectInfo . isRSC ,
111+ typescript : projectInfo . isTsx ,
112+ tailwindVersion : projectInfo . tailwindVersion ?? null ,
113+ tailwindConfig : projectInfo . tailwindConfigFile ?? null ,
114+ tailwindCss : projectInfo . tailwindCssFile ?? null ,
115+ importAlias : projectInfo . aliasPrefix ?? null ,
116+ }
117+ : null ,
118+ config : config
119+ ? {
120+ style : config . style ,
121+ base,
122+ rsc : config . rsc ,
123+ typescript : config . tsx ,
124+ iconLibrary : config . iconLibrary ?? null ,
125+ rtl : config . rtl ?? false ,
126+ menuColor : config . menuColor ?? null ,
127+ menuAccent : config . menuAccent ?? null ,
128+ aliases : {
129+ components : config . aliases . components ,
130+ utils : config . aliases . utils ,
131+ ui : config . aliases . ui ?? null ,
132+ lib : config . aliases . lib ?? null ,
133+ hooks : config . aliases . hooks ?? null ,
134+ } ,
135+ resolvedPaths : {
136+ cwd : config . resolvedPaths . cwd ,
137+ tailwindConfig : config . resolvedPaths . tailwindConfig || null ,
138+ tailwindCss : config . resolvedPaths . tailwindCss || null ,
139+ utils : config . resolvedPaths . utils ,
140+ components : config . resolvedPaths . components ,
141+ lib : config . resolvedPaths . lib ,
142+ hooks : config . resolvedPaths . hooks ,
143+ ui : config . resolvedPaths . ui ,
144+ } ,
145+ registries : getRegistries ( config . registries ) ,
146+ }
147+ : null ,
148+ components,
149+ links : {
150+ docs : "https://ui.shadcn.com/docs" ,
151+ components : `https://ui.shadcn.com/docs/components/${ base } /[component].md` ,
152+ ui : `${ GITHUB_RAW_BASE } /${ base } /ui/[component].tsx` ,
153+ examples : `${ GITHUB_RAW_BASE } /${ base } /examples/[component]-example.tsx` ,
154+ schema : "https://ui.shadcn.com/schema.json" ,
155+ } ,
156+ }
157+ }
158+
159+ function printInfo ( data : ReturnType < typeof collectInfo > ) {
160+ // Project.
161+ logger . log ( highlighter . info ( "Project" ) )
162+ if ( data . project ) {
163+ printEntries ( {
164+ framework : `${ data . project . framework } (${ data . project . frameworkName } )` ,
165+ frameworkVersion : data . project . frameworkVersion ?? "-" ,
166+ srcDirectory : data . project . srcDirectory ? "Yes" : "No" ,
167+ rsc : data . project . rsc ? "Yes" : "No" ,
168+ typescript : data . project . typescript ? "Yes" : "No" ,
169+ tailwindVersion : data . project . tailwindVersion ?? "-" ,
170+ tailwindConfig : data . project . tailwindConfig ?? "-" ,
171+ tailwindCss : data . project . tailwindCss ?? "-" ,
172+ importAlias : data . project . importAlias ?? "-" ,
173+ } )
174+ } else {
175+ logger . log ( " No project info detected." )
176+ }
177+
178+ // Config.
179+ logger . break ( )
180+ logger . log ( highlighter . info ( "Configuration" ) )
181+ if ( data . config ) {
182+ printEntries ( {
183+ style : data . config . style ,
184+ base : data . config . base ,
185+ rsc : data . config . rsc ? "Yes" : "No" ,
186+ typescript : data . config . typescript ? "Yes" : "No" ,
187+ iconLibrary : data . config . iconLibrary ?? "-" ,
188+ rtl : data . config . rtl ? "Yes" : "No" ,
189+ menuColor : data . config . menuColor ?? "-" ,
190+ menuAccent : data . config . menuAccent ?? "-" ,
191+ } )
192+
193+ // Aliases.
194+ logger . break ( )
195+ logger . log ( highlighter . info ( "Aliases" ) )
196+ printEntries ( {
197+ components : data . config . aliases . components ,
198+ utils : data . config . aliases . utils ,
199+ ui : data . config . aliases . ui ?? "-" ,
200+ lib : data . config . aliases . lib ?? "-" ,
201+ hooks : data . config . aliases . hooks ?? "-" ,
202+ } )
203+
204+ // Resolved paths.
205+ logger . break ( )
206+ logger . log ( highlighter . info ( "Resolved Paths" ) )
207+ printEntries ( {
208+ cwd : data . config . resolvedPaths . cwd ,
209+ tailwindConfig : data . config . resolvedPaths . tailwindConfig ?? "-" ,
210+ tailwindCss : data . config . resolvedPaths . tailwindCss ?? "-" ,
211+ utils : data . config . resolvedPaths . utils ,
212+ components : data . config . resolvedPaths . components ,
213+ lib : data . config . resolvedPaths . lib ,
214+ hooks : data . config . resolvedPaths . hooks ,
215+ ui : data . config . resolvedPaths . ui ,
216+ } )
217+
218+ // Registries.
219+ if ( Object . keys ( data . config . registries ) . length > 0 ) {
220+ logger . break ( )
221+ logger . log ( highlighter . info ( "Registries" ) )
222+ printEntries ( data . config . registries )
223+ }
224+ } else {
225+ logger . log ( " No components.json found." )
226+ }
227+
228+ // Installed components.
229+ logger . break ( )
230+ logger . log ( highlighter . info ( "Installed Components" ) )
231+ if ( data . components . length > 0 ) {
232+ logger . log ( ` ${ data . components . join ( ", " ) } ` )
233+ } else {
234+ logger . log ( " No components installed." )
235+ }
236+
237+ // Links.
238+ logger . break ( )
239+ logger . log ( highlighter . info ( "Links" ) )
240+ printEntries ( data . links )
241+
242+ logger . break ( )
243+ }
244+
245+ function printEntries ( entries : Record < string , string > ) {
246+ const maxKeyLength = Math . max ( ...Object . keys ( entries ) . map ( ( k ) => k . length ) )
247+ for ( const [ key , value ] of Object . entries ( entries ) ) {
248+ logger . log ( ` ${ key . padEnd ( maxKeyLength + 2 ) } ${ value } ` )
249+ }
250+ }
0 commit comments