@@ -122,6 +122,15 @@ async function listAccessibleProjectsV2(projectInfo) {
122122 closed
123123 url
124124 }
125+ edges {
126+ node {
127+ id
128+ number
129+ title
130+ closed
131+ url
132+ }
133+ }
125134 }` ;
126135
127136 if ( projectInfo . scope === "orgs" ) {
@@ -134,8 +143,30 @@ async function listAccessibleProjectsV2(projectInfo) {
134143 { login : projectInfo . ownerLogin }
135144 ) ;
136145 const conn = result && result . organization && result . organization . projectsV2 ;
137- const nodes = conn && Array . isArray ( conn . nodes ) ? conn . nodes . filter ( Boolean ) : [ ] ;
138- return { nodes, totalCount : conn && conn . totalCount } ;
146+ const rawNodes = conn && Array . isArray ( conn . nodes ) ? conn . nodes : [ ] ;
147+ const rawEdges = conn && Array . isArray ( conn . edges ) ? conn . edges : [ ] ;
148+
149+ const nodeNodes = rawNodes . filter ( Boolean ) ;
150+ const edgeNodes = rawEdges . map ( e => e && e . node ) . filter ( Boolean ) ;
151+
152+ /** @type {Map<string, any> } */
153+ const unique = new Map ( ) ;
154+ for ( const n of [ ...nodeNodes , ...edgeNodes ] ) {
155+ if ( n && typeof n . id === "string" ) {
156+ unique . set ( n . id , n ) ;
157+ }
158+ }
159+
160+ return {
161+ nodes : Array . from ( unique . values ( ) ) ,
162+ totalCount : conn && conn . totalCount ,
163+ diagnostics : {
164+ rawNodesCount : rawNodes . length ,
165+ nullNodesCount : rawNodes . length - nodeNodes . length ,
166+ rawEdgesCount : rawEdges . length ,
167+ nullEdgeNodesCount : rawEdges . filter ( e => ! e || ! e . node ) . length ,
168+ } ,
169+ } ;
139170 }
140171
141172 const result = await github . graphql (
@@ -147,8 +178,30 @@ async function listAccessibleProjectsV2(projectInfo) {
147178 { login : projectInfo . ownerLogin }
148179 ) ;
149180 const conn = result && result . user && result . user . projectsV2 ;
150- const nodes = conn && Array . isArray ( conn . nodes ) ? conn . nodes . filter ( Boolean ) : [ ] ;
151- return { nodes, totalCount : conn && conn . totalCount } ;
181+ const rawNodes = conn && Array . isArray ( conn . nodes ) ? conn . nodes : [ ] ;
182+ const rawEdges = conn && Array . isArray ( conn . edges ) ? conn . edges : [ ] ;
183+
184+ const nodeNodes = rawNodes . filter ( Boolean ) ;
185+ const edgeNodes = rawEdges . map ( e => e && e . node ) . filter ( Boolean ) ;
186+
187+ /** @type {Map<string, any> } */
188+ const unique = new Map ( ) ;
189+ for ( const n of [ ...nodeNodes , ...edgeNodes ] ) {
190+ if ( n && typeof n . id === "string" ) {
191+ unique . set ( n . id , n ) ;
192+ }
193+ }
194+
195+ return {
196+ nodes : Array . from ( unique . values ( ) ) ,
197+ totalCount : conn && conn . totalCount ,
198+ diagnostics : {
199+ rawNodesCount : rawNodes . length ,
200+ nullNodesCount : rawNodes . length - nodeNodes . length ,
201+ rawEdgesCount : rawEdges . length ,
202+ nullEdgeNodesCount : rawEdges . filter ( e => ! e || ! e . node ) . length ,
203+ } ,
204+ } ;
152205}
153206
154207/**
@@ -170,6 +223,25 @@ function summarizeProjectsV2(projects, limit = 20) {
170223 return normalized . length > 0 ? normalized . join ( "; " ) : "(none)" ;
171224}
172225
226+ /**
227+ * Summarize a projectsV2 listing call when it returned no readable projects.
228+ * @param {{ totalCount?: number, diagnostics?: {rawNodesCount: number, nullNodesCount: number, rawEdgesCount: number, nullEdgeNodesCount: number} } } list
229+ * @returns {string }
230+ */
231+ function summarizeEmptyProjectsV2List ( list ) {
232+ const total = typeof list . totalCount === "number" ? list . totalCount : undefined ;
233+ const d = list && list . diagnostics ;
234+ const diag = d
235+ ? ` nodes=${ d . rawNodesCount } (null=${ d . nullNodesCount } ), edges=${ d . rawEdgesCount } (nullNode=${ d . nullEdgeNodesCount } )`
236+ : "" ;
237+
238+ if ( typeof total === "number" && total > 0 ) {
239+ return `(none; totalCount=${ total } but returned 0 readable project nodes${ diag } . This often indicates the token can see the org/user but lacks Projects v2 access, or the org enforces SSO and the token is not authorized.)` ;
240+ }
241+
242+ return `(none${ diag } )` ;
243+ }
244+
173245/**
174246 * Resolve a Projects v2 project by URL-parsed {scope, ownerLogin, number}.
175247 * Hybrid strategy:
@@ -231,7 +303,7 @@ async function resolveProjectV2(projectInfo, projectNumberInt) {
231303 return found ;
232304 }
233305
234- const summary = summarizeProjectsV2 ( nodes ) ;
306+ const summary = nodes . length > 0 ? summarizeProjectsV2 ( nodes ) : summarizeEmptyProjectsV2List ( list ) ;
235307 const total = typeof list . totalCount === "number" ? ` (totalCount=${ list . totalCount } )` : "" ;
236308 const who = projectInfo . scope === "orgs" ? `org ${ projectInfo . ownerLogin } ` : `user ${ projectInfo . ownerLogin } ` ;
237309 throw new Error (
0 commit comments