@@ -39,18 +39,35 @@ export class YarnPackageManagerAdapter extends AbstractPackageManagerAdapter {
3939 [ "yarn" , "workspaces" , primary , "--json" ] ,
4040 [ "yarn" , "workspaces" , fallback , "--json" ] ,
4141 ] ;
42+ let lastOutput : string | undefined ;
43+ let hasRecognizedOutput = false ;
44+ let hasSuccessfulCommand = false ;
4245
4346 for ( const args of yarnCommands ) {
4447 try {
4548 const output = await this . execCommand ( args , dirPath , true ) ;
46- if ( this . yarnWorkspacesOutputHasEntries ( output ) ) {
49+ hasSuccessfulCommand = true ;
50+ lastOutput = output ;
51+
52+ const analysis = this . analyzeYarnWorkspacesOutput ( output ) ;
53+ if ( analysis . hasEntries ) {
4754 return true ;
4855 }
56+
57+ if ( analysis . hasRecognizedData ) {
58+ hasRecognizedOutput = true ;
59+ }
4960 } catch {
5061 // Try next command
5162 }
5263 }
5364
65+ if ( hasSuccessfulCommand && ! hasRecognizedOutput ) {
66+ throw new Error (
67+ `Unexpected output from "yarn workspaces": ${ lastOutput ?? "<empty>" } `
68+ ) ;
69+ }
70+
5471 return false ;
5572 }
5673
@@ -74,28 +91,46 @@ export class YarnPackageManagerAdapter extends AbstractPackageManagerAdapter {
7491 } catch {
7592 return false ;
7693 }
94+
7795 }
7896
7997 async getNodeModulesPath ( dirPath : string ) : Promise < string > {
8098 return join ( dirPath , "node_modules" ) ;
8199 }
82100
83- private yarnWorkspacesOutputHasEntries ( output : string ) : boolean {
101+ private analyzeYarnWorkspacesOutput (
102+ output : string
103+ ) : { hasEntries : boolean ; hasRecognizedData : boolean } {
84104 const entries = this . parseJsonLines ( output ) ;
85105
106+ if ( entries . length === 0 ) {
107+ return { hasEntries : false , hasRecognizedData : false } ;
108+ }
109+
86110 let workspaceListCount = 0 ;
111+ let hasRecognizedData = false ;
87112
88113 for ( const entry of entries ) {
89114 if ( ! entry || typeof entry !== "object" ) {
90115 continue ;
91116 }
92117
118+ const type = ( entry as { type ?: string } ) . type ;
119+ if ( type === "error" || type === "warning" ) {
120+ continue ;
121+ }
122+
93123 const data = ( entry as { data ?: unknown } ) . data ?? entry ;
94124
95125 const parsedData = this . parseMaybeJsonString ( data ) ;
96126 if ( parsedData && typeof parsedData === "object" ) {
127+ hasRecognizedData = true ;
97128 if ( this . hasWorkspaceMap ( parsedData ) ) {
98- return true ;
129+ return { hasEntries : true , hasRecognizedData } ;
130+ }
131+
132+ if ( this . isWorkspaceInfoMap ( parsedData ) ) {
133+ return { hasEntries : true , hasRecognizedData } ;
99134 }
100135
101136 if ( this . isWorkspaceListEntry ( parsedData ) ) {
@@ -105,15 +140,9 @@ export class YarnPackageManagerAdapter extends AbstractPackageManagerAdapter {
105140 }
106141
107142 if ( workspaceListCount > 0 ) {
108- return true ;
143+ return { hasEntries : true , hasRecognizedData } ;
109144 }
110-
111- const parsedOutput = this . parseJsonObjectFromOutput ( output ) ;
112- if ( parsedOutput && typeof parsedOutput === "object" ) {
113- return Object . keys ( parsedOutput as Record < string , unknown > ) . length > 0 ;
114- }
115-
116- return false ;
145+ return { hasEntries : false , hasRecognizedData } ;
117146 }
118147
119148 private parseMaybeJsonString ( value : unknown ) : unknown {
@@ -137,29 +166,33 @@ export class YarnPackageManagerAdapter extends AbstractPackageManagerAdapter {
137166 return ! ! workspaces && Object . keys ( workspaces ) . length > 0 ;
138167 }
139168
140- private parseJsonObjectFromOutput ( output : string ) : unknown {
141- const start = output . indexOf ( "{" ) ;
142- const end = output . lastIndexOf ( "}" ) ;
143-
144- if ( start < 0 || end <= start ) {
145- return undefined ;
169+ private isWorkspaceListEntry ( value : unknown ) : boolean {
170+ if ( ! value || typeof value !== "object" ) {
171+ return false ;
146172 }
147173
148- const slice = output . slice ( start , end + 1 ) . trim ( ) ;
149- try {
150- return JSON . parse ( slice ) as unknown ;
151- } catch {
152- return undefined ;
153- }
174+ const entry = value as { name ?: string ; location ?: string } ;
175+ return typeof entry . name === "string" && typeof entry . location === "string" && entry . location !== "." ;
154176 }
155177
156- private isWorkspaceListEntry ( value : unknown ) : boolean {
178+ private isWorkspaceInfoMap ( value : unknown ) : boolean {
157179 if ( ! value || typeof value !== "object" ) {
158180 return false ;
159181 }
160182
161- const entry = value as { name ?: string ; location ?: string } ;
162- return typeof entry . name === "string" && typeof entry . location === "string" && entry . location !== "." ;
183+ const entries = Object . values ( value as Record < string , unknown > ) ;
184+ if ( entries . length === 0 ) {
185+ return false ;
186+ }
187+
188+ return entries . some ( ( entry ) => {
189+ if ( ! entry || typeof entry !== "object" ) {
190+ return false ;
191+ }
192+
193+ const location = ( entry as { location ?: unknown } ) . location ;
194+ return typeof location === "string" && location . length > 0 && location !== "." ;
195+ } ) ;
163196 }
164197
165198 private yarnListOutputHasPackage ( output : string , packageName : string ) : boolean {
@@ -174,6 +207,13 @@ export class YarnPackageManagerAdapter extends AbstractPackageManagerAdapter {
174207 const trees = data ?. trees ?? ( entry as { trees ?: Array < { name : string } > } ) . trees ;
175208
176209 if ( ! trees ) {
210+ const children = ( entry as { children ?: Record < string , unknown > } ) . children ;
211+ if ( children ) {
212+ const childKeys = Object . keys ( children ) ;
213+ if ( childKeys . some ( ( key ) => key . startsWith ( packageName + "@" ) ) ) {
214+ return true ;
215+ }
216+ }
177217 continue ;
178218 }
179219
0 commit comments