@@ -135,6 +135,12 @@ const detectFramework = (dependencies: Record<string, string>): Framework => {
135135
136136const isCatalogReference = ( version : string ) : boolean => version . startsWith ( "catalog:" ) ;
137137
138+ const extractCatalogName = ( version : string ) : string | null => {
139+ if ( ! isCatalogReference ( version ) ) return null ;
140+ const name = version . slice ( "catalog:" . length ) . trim ( ) ;
141+ return name . length > 0 ? name : null ;
142+ } ;
143+
138144const resolveVersionFromCatalog = (
139145 catalog : Record < string , unknown > ,
140146 packageName : string ,
@@ -144,7 +150,112 @@ const resolveVersionFromCatalog = (
144150 return null ;
145151} ;
146152
147- const resolveCatalogVersion = ( packageJson : PackageJson , packageName : string ) : string | null => {
153+ interface CatalogCollection {
154+ defaultCatalog : Record < string , string > ;
155+ namedCatalogs : Record < string , Record < string , string > > ;
156+ }
157+
158+ const parsePnpmWorkspaceCatalogs = ( rootDirectory : string ) : CatalogCollection => {
159+ const workspacePath = path . join ( rootDirectory , "pnpm-workspace.yaml" ) ;
160+ if ( ! isFile ( workspacePath ) ) return { defaultCatalog : { } , namedCatalogs : { } } ;
161+
162+ const content = fs . readFileSync ( workspacePath , "utf-8" ) ;
163+ const defaultCatalog : Record < string , string > = { } ;
164+ const namedCatalogs : Record < string , Record < string , string > > = { } ;
165+
166+ let currentSection : "none" | "catalog" | "catalogs" | "named-catalog" = "none" ;
167+ let currentCatalogName = "" ;
168+
169+ for ( const line of content . split ( "\n" ) ) {
170+ const trimmed = line . trim ( ) ;
171+ if ( trimmed . length === 0 || trimmed . startsWith ( "#" ) ) continue ;
172+
173+ const indentLevel = line . search ( / \S / ) ;
174+
175+ if ( indentLevel === 0 && trimmed === "catalog:" ) {
176+ currentSection = "catalog" ;
177+ continue ;
178+ }
179+ if ( indentLevel === 0 && trimmed === "catalogs:" ) {
180+ currentSection = "catalogs" ;
181+ continue ;
182+ }
183+ if ( indentLevel === 0 ) {
184+ currentSection = "none" ;
185+ continue ;
186+ }
187+
188+ if ( currentSection === "catalog" && indentLevel > 0 ) {
189+ const colonIndex = trimmed . indexOf ( ":" ) ;
190+ if ( colonIndex > 0 ) {
191+ const key = trimmed . slice ( 0 , colonIndex ) . trim ( ) . replace ( / [ " ' ] / g, "" ) ;
192+ const value = trimmed
193+ . slice ( colonIndex + 1 )
194+ . trim ( )
195+ . replace ( / [ " ' ] / g, "" ) ;
196+ if ( key && value ) defaultCatalog [ key ] = value ;
197+ }
198+ continue ;
199+ }
200+
201+ if ( currentSection === "catalogs" && indentLevel > 0 ) {
202+ if ( trimmed . endsWith ( ":" ) && ! trimmed . includes ( " " ) ) {
203+ currentCatalogName = trimmed . slice ( 0 , - 1 ) . replace ( / [ " ' ] / g, "" ) ;
204+ currentSection = "named-catalog" ;
205+ namedCatalogs [ currentCatalogName ] = { } ;
206+ continue ;
207+ }
208+ }
209+
210+ if ( currentSection === "named-catalog" && indentLevel > 0 ) {
211+ if ( indentLevel <= 2 && trimmed . endsWith ( ":" ) && ! trimmed . includes ( " " ) ) {
212+ currentCatalogName = trimmed . slice ( 0 , - 1 ) . replace ( / [ " ' ] / g, "" ) ;
213+ namedCatalogs [ currentCatalogName ] = { } ;
214+ continue ;
215+ }
216+ const colonIndex = trimmed . indexOf ( ":" ) ;
217+ if ( colonIndex > 0 && currentCatalogName ) {
218+ const key = trimmed . slice ( 0 , colonIndex ) . trim ( ) . replace ( / [ " ' ] / g, "" ) ;
219+ const value = trimmed
220+ . slice ( colonIndex + 1 )
221+ . trim ( )
222+ . replace ( / [ " ' ] / g, "" ) ;
223+ if ( key && value ) namedCatalogs [ currentCatalogName ] [ key ] = value ;
224+ }
225+ }
226+ }
227+
228+ return { defaultCatalog, namedCatalogs } ;
229+ } ;
230+
231+ const resolveCatalogVersionFromCollection = (
232+ catalogs : CatalogCollection ,
233+ packageName : string ,
234+ catalogReference ?: string | null ,
235+ ) : string | null => {
236+ if ( catalogReference ) {
237+ const namedCatalog = catalogs . namedCatalogs [ catalogReference ] ;
238+ if ( namedCatalog ?. [ packageName ] ) return namedCatalog [ packageName ] ;
239+ }
240+
241+ if ( catalogs . defaultCatalog [ packageName ] ) return catalogs . defaultCatalog [ packageName ] ;
242+
243+ for ( const namedCatalog of Object . values ( catalogs . namedCatalogs ) ) {
244+ if ( namedCatalog [ packageName ] ) return namedCatalog [ packageName ] ;
245+ }
246+
247+ return null ;
248+ } ;
249+
250+ const resolveCatalogVersion = (
251+ packageJson : PackageJson ,
252+ packageName : string ,
253+ rootDirectory ?: string ,
254+ ) : string | null => {
255+ const allDependencies = collectAllDependencies ( packageJson ) ;
256+ const rawVersion = allDependencies [ packageName ] ;
257+ const catalogName = rawVersion ? extractCatalogName ( rawVersion ) : null ;
258+
148259 const raw = packageJson as Record < string , unknown > ;
149260
150261 if ( isPlainObject ( raw . catalog ) ) {
@@ -153,6 +264,13 @@ const resolveCatalogVersion = (packageJson: PackageJson, packageName: string): s
153264 }
154265
155266 if ( isPlainObject ( raw . catalogs ) ) {
267+ if ( catalogName && isPlainObject ( ( raw . catalogs as Record < string , unknown > ) [ catalogName ] ) ) {
268+ const version = resolveVersionFromCatalog (
269+ ( raw . catalogs as Record < string , unknown > ) [ catalogName ] as Record < string , unknown > ,
270+ packageName ,
271+ ) ;
272+ if ( version ) return version ;
273+ }
156274 for ( const catalogEntries of Object . values ( raw . catalogs ) ) {
157275 if ( isPlainObject ( catalogEntries ) ) {
158276 const version = resolveVersionFromCatalog ( catalogEntries , packageName ) ;
@@ -161,6 +279,21 @@ const resolveCatalogVersion = (packageJson: PackageJson, packageName: string): s
161279 }
162280 }
163281
282+ const workspaces = packageJson . workspaces ;
283+ if ( workspaces && ! Array . isArray ( workspaces ) && isPlainObject ( workspaces . catalog ) ) {
284+ const version = resolveVersionFromCatalog (
285+ workspaces . catalog as Record < string , unknown > ,
286+ packageName ,
287+ ) ;
288+ if ( version ) return version ;
289+ }
290+
291+ if ( rootDirectory ) {
292+ const pnpmCatalogs = parsePnpmWorkspaceCatalogs ( rootDirectory ) ;
293+ const pnpmVersion = resolveCatalogVersionFromCollection ( pnpmCatalogs , packageName , catalogName ) ;
294+ if ( pnpmVersion ) return pnpmVersion ;
295+ }
296+
164297 return null ;
165298} ;
166299
@@ -252,7 +385,7 @@ const findDependencyInfoFromMonorepoRoot = (directory: string): DependencyInfo =
252385
253386 const rootPackageJson = readPackageJson ( monorepoPackageJsonPath ) ;
254387 const rootInfo = extractDependencyInfo ( rootPackageJson ) ;
255- const catalogVersion = resolveCatalogVersion ( rootPackageJson , "react" ) ;
388+ const catalogVersion = resolveCatalogVersion ( rootPackageJson , "react" , monorepoRoot ) ;
256389 const workspaceInfo = findReactInWorkspaces ( monorepoRoot , rootPackageJson ) ;
257390
258391 return {
@@ -342,6 +475,11 @@ export const listWorkspacePackages = (rootDirectory: string): WorkspacePackage[]
342475
343476 const packages : WorkspacePackage [ ] = [ ] ;
344477
478+ if ( hasReactDependency ( packageJson ) ) {
479+ const rootName = packageJson . name ?? path . basename ( rootDirectory ) ;
480+ packages . push ( { name : rootName , directory : rootDirectory } ) ;
481+ }
482+
345483 for ( const pattern of patterns ) {
346484 const directories = resolveWorkspaceDirectories ( rootDirectory , pattern ) ;
347485 for ( const workspaceDirectory of directories ) {
@@ -406,7 +544,18 @@ export const discoverProject = (directory: string): ProjectInfo => {
406544 let { reactVersion, framework } = extractDependencyInfo ( packageJson ) ;
407545
408546 if ( ! reactVersion ) {
409- reactVersion = resolveCatalogVersion ( packageJson , "react" ) ;
547+ reactVersion = resolveCatalogVersion ( packageJson , "react" , directory ) ;
548+ }
549+
550+ if ( ! reactVersion ) {
551+ const monorepoRoot = findMonorepoRoot ( directory ) ;
552+ if ( monorepoRoot ) {
553+ const monorepoPackageJsonPath = path . join ( monorepoRoot , "package.json" ) ;
554+ if ( isFile ( monorepoPackageJsonPath ) ) {
555+ const rootPackageJson = readPackageJson ( monorepoPackageJsonPath ) ;
556+ reactVersion = resolveCatalogVersion ( rootPackageJson , "react" , monorepoRoot ) ;
557+ }
558+ }
410559 }
411560
412561 if ( ! reactVersion || framework === "unknown" ) {
0 commit comments