import { flags as flagTypes } from "@oclif/command" ; import * as dot from "dot-object" ; import createDebugger from "debug" ; import * as ProgressBar from "progress" ; import * as jsonexport from "jsonexport" ; import BaseCommand from "../../base" ; import { CommentsList , RepositoryPullRequests , PullRequest , } from "../../github" ; const debug = createDebugger ( "exporter:repo:pulls" ) ; const LIST_PULLS_QUERY = `query listPulls($owner: String!, $repo: String!, $per_page: Int = 10, $after: String) {
  repository(owner: $owner, name: $repo) {
    pullRequests(first: $per_page, after: $after) {
      nodes {
        reviewDecision
        merged
        mergedAt
        mergedBy {
          login
        }
        assignees(first: 10) {
          nodes {
            login
          }
        }
        author {
          login
        }
        comments(first: 10) {
          nodes {
            author {
              login
            }
            body
            createdAt
          }
          pageInfo {
            endCursor
            hasNextPage
          }
          totalCount
        }
        createdAt
        id
        labels(first: 100) {
          nodes {
            name
          }
        }
        state
        title
        updatedAt
        milestone {
          title
          state
          url
        }
        closedAt
      }
      pageInfo {
        endCursor
        hasNextPage
      }
      totalCount
    }
  }
}
` ; const LIST_COMMENTS_QUERY = `query listComments($id: ID!, $per_page: Int = 100, $after: String) {
  node(id: $id) {
    ... on PullRequest {
      comments(first: $per_page, after: $after) {
        nodes {
          author {
            login
          }
          body
          createdAt
        }
        pageInfo {
          endCursor
          hasNextPage
        }
        totalCount
      }
    }
  }
}
` ; export default class RepoPulls extends BaseCommand { static description = "Export GitHub Pull Requests for a repository" ; static flags = { ... BaseCommand . flags , owner : flagTypes . string ( { dependsOn : [ "repo" ] , description : "GitHub repository owner" , required : true , } ) , repo : flagTypes . string ( { dependsOn : [ "owner" ] , description : "GitHub repository name" , required : true , } ) , since : flagTypes . string ( { description : "search pull requests created after yyyy-mm-dd" , } ) , until : flagTypes . string ( { description : "search pull requests created before yyyy-mm-dd" , } ) , } ; async fetchPulls ( query , owner , repo ) { const pulls = [ ] ; let results ; let cursor ; let progress ; debug ( "Pulling pull requests from API" ) ; do { results = await this . github . graphql ( query , { owner , repo , after : cursor , } ) ; cursor = results . repository . pullRequests . pageInfo . endCursor ; if ( ! progress ) { if ( results . repository . pullRequests . totalCount === 0 ) { this . warn ( "No pull requests found" ) ; process . exit ( 1 ) ; } progress = new ProgressBar ( "fetching pulls [:bar] :current/:total :percent" , { complete : "=" , incomplete : " " , width : 20 , total : results . repository . pullRequests . totalCount , } ) ; } progress . tick ( results . repository . pullRequests . nodes . length ) ; pulls . push ( ... results . repository . pullRequests . nodes ) ; } while ( results . repository . pullRequests . pageInfo . hasNextPage ) ; return pulls ; } async fetchComments ( query , pull , progress ) { let results = { node : { comments : pull . comments , } , } ; while ( results . node . comments . pageInfo . hasNextPage ) { results = await this . github . graphql ( query , { id : pull . id , after : results . node . comments . pageInfo . endCursor , } ) ; pull . comments . nodes . push ( ... results . node . comments . nodes ) ; progress . tick ( results . node . comments . nodes . length ) ; } } async run ( ) { const { flags } = this . parse ( RepoPulls ) ; const { owner , repo , format , since , until } = flags ; let pulls = await this . fetchPulls ( LIST_PULLS_QUERY , owner , repo ) ; if ( since ) { pulls = pulls . filter ( ( pull ) => { return + new Date ( pull . createdAt ) >= + new Date ( since ) ; } ) ; } if ( until ) { pulls = pulls . filter ( ( pull ) => { return + new Date ( until ) >= + new Date ( pull . createdAt ) ; } ) ; } const pullsWithComments = pulls . filter ( ( issue ) => issue . comments . totalCount - issue . comments . nodes . length > 0 ) ; if ( pullsWithComments . length > 0 ) { const remainingComments = pullsWithComments . map ( ( issue ) => { return issue . comments . totalCount - issue . comments . nodes . length ; } ) . reduce ( ( x , y ) => x + y ) ; const progress = new ProgressBar ( "fetching comments [:bar] :current/:total :percent" , { complete : "=" , incomplete : " " , width : 20 , total : remainingComments , } ) ; const promises = pullsWithComments . map ( ( issue ) => this . fetchComments ( LIST_COMMENTS_QUERY , issue , progress ) ) ; await Promise . all ( promises ) ; } for ( const pull of pulls ) { dot . del ( "id" , pull ) ; dot . move ( "assignees.nodes" , "assignees" , pull ) ; dot . move ( "comments.nodes" , "comments" , pull ) ; dot . move ( "labels.nodes" , "labels" , pull ) ; pull . labels = pull . labels . map ( ( { name } ) => name ) . join ( ", " ) ; } if ( format === "JSONL" ) { for ( const pull of pulls ) { process . stdout . write ( `${ JSON . stringify ( pull ) }\n` ) ; } } else if ( format === "JSON" ) { process . stdout . write ( JSON . stringify ( pulls ) ) ; } else if ( format === "CSV" ) { const csv = await jsonexport ( pulls , { fillGaps : true } ) ; process . stdout . write ( csv ) ; } } }