@@ -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,53 @@ 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 {
84- const entries = this . parseJsonLines ( output ) ;
101+ private analyzeYarnWorkspacesOutput (
102+ output : string
103+ ) : { hasEntries : boolean ; hasRecognizedData : boolean } {
104+ let entries = this . parseJsonLines ( output ) ;
105+
106+ if ( entries . length === 0 ) {
107+ const jsonBlock = this . extractJsonBlock ( output ) ;
108+ if ( jsonBlock && typeof jsonBlock === "object" ) {
109+ entries = [ jsonBlock ] ;
110+ }
111+ }
112+
113+ if ( entries . length === 0 ) {
114+ return { hasEntries : false , hasRecognizedData : false } ;
115+ }
85116
86117 let workspaceListCount = 0 ;
118+ let hasRecognizedData = false ;
87119
88120 for ( const entry of entries ) {
89121 if ( ! entry || typeof entry !== "object" ) {
90122 continue ;
91123 }
92124
125+ const type = ( entry as { type ?: string } ) . type ;
126+ if ( type === "error" || type === "warning" ) {
127+ continue ;
128+ }
129+
93130 const data = ( entry as { data ?: unknown } ) . data ?? entry ;
94131
95132 const parsedData = this . parseMaybeJsonString ( data ) ;
96133 if ( parsedData && typeof parsedData === "object" ) {
134+ hasRecognizedData = true ;
97135 if ( this . hasWorkspaceMap ( parsedData ) ) {
98- return true ;
136+ return { hasEntries : true , hasRecognizedData } ;
137+ }
138+
139+ if ( this . isWorkspaceInfoMap ( parsedData ) ) {
140+ return { hasEntries : true , hasRecognizedData } ;
99141 }
100142
101143 if ( this . isWorkspaceListEntry ( parsedData ) ) {
@@ -105,15 +147,9 @@ export class YarnPackageManagerAdapter extends AbstractPackageManagerAdapter {
105147 }
106148
107149 if ( workspaceListCount > 0 ) {
108- return true ;
150+ return { hasEntries : true , hasRecognizedData } ;
109151 }
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 ;
152+ return { hasEntries : false , hasRecognizedData } ;
117153 }
118154
119155 private parseMaybeJsonString ( value : unknown ) : unknown {
@@ -128,31 +164,35 @@ export class YarnPackageManagerAdapter extends AbstractPackageManagerAdapter {
128164 }
129165 }
130166
131- private hasWorkspaceMap ( value : unknown ) : boolean {
132- if ( ! value || typeof value !== "object" ) {
133- return false ;
134- }
135-
136- const workspaces = ( value as { workspaces ?: Record < string , unknown > } ) . workspaces ;
137- return ! ! workspaces && Object . keys ( workspaces ) . length > 0 ;
138- }
139-
140- private parseJsonObjectFromOutput ( output : string ) : unknown {
167+ private extractJsonBlock ( output : string ) : unknown {
141168 const start = output . indexOf ( "{" ) ;
142169 const end = output . lastIndexOf ( "}" ) ;
143170
144- if ( start < 0 || end <= start ) {
171+ if ( start === - 1 || end === - 1 || end <= start ) {
172+ return undefined ;
173+ }
174+
175+ const candidate = output . slice ( start , end + 1 ) . trim ( ) ;
176+ if ( candidate . length === 0 ) {
145177 return undefined ;
146178 }
147179
148- const slice = output . slice ( start , end + 1 ) . trim ( ) ;
149180 try {
150- return JSON . parse ( slice ) as unknown ;
181+ return JSON . parse ( candidate ) as unknown ;
151182 } catch {
152183 return undefined ;
153184 }
154185 }
155186
187+ private hasWorkspaceMap ( value : unknown ) : boolean {
188+ if ( ! value || typeof value !== "object" ) {
189+ return false ;
190+ }
191+
192+ const workspaces = ( value as { workspaces ?: Record < string , unknown > } ) . workspaces ;
193+ return ! ! workspaces && Object . keys ( workspaces ) . length > 0 ;
194+ }
195+
156196 private isWorkspaceListEntry ( value : unknown ) : boolean {
157197 if ( ! value || typeof value !== "object" ) {
158198 return false ;
@@ -162,6 +202,26 @@ export class YarnPackageManagerAdapter extends AbstractPackageManagerAdapter {
162202 return typeof entry . name === "string" && typeof entry . location === "string" && entry . location !== "." ;
163203 }
164204
205+ private isWorkspaceInfoMap ( value : unknown ) : boolean {
206+ if ( ! value || typeof value !== "object" ) {
207+ return false ;
208+ }
209+
210+ const entries = Object . values ( value as Record < string , unknown > ) ;
211+ if ( entries . length === 0 ) {
212+ return false ;
213+ }
214+
215+ return entries . some ( ( entry ) => {
216+ if ( ! entry || typeof entry !== "object" ) {
217+ return false ;
218+ }
219+
220+ const location = ( entry as { location ?: unknown } ) . location ;
221+ return typeof location === "string" && location . length > 0 && location !== "." ;
222+ } ) ;
223+ }
224+
165225 private yarnListOutputHasPackage ( output : string , packageName : string ) : boolean {
166226 const entries = this . parseJsonLines ( output ) ;
167227
@@ -174,6 +234,13 @@ export class YarnPackageManagerAdapter extends AbstractPackageManagerAdapter {
174234 const trees = data ?. trees ?? ( entry as { trees ?: Array < { name : string } > } ) . trees ;
175235
176236 if ( ! trees ) {
237+ const children = ( entry as { children ?: Record < string , unknown > } ) . children ;
238+ if ( children ) {
239+ const childKeys = Object . keys ( children ) ;
240+ if ( childKeys . some ( ( key ) => key . startsWith ( packageName + "@" ) ) ) {
241+ return true ;
242+ }
243+ }
177244 continue ;
178245 }
179246
0 commit comments