$shibayu36->blog;

クラスター株式会社のソフトウェアエンジニアです。エンジニアリングや読書などについて書いています。

GraphQL APIを使って、特定のGitHub Teamが持つレポジトリ一覧を一発で取得する

最近 merged-pr-statを使ってマージされたPR情報を集計したり、レビューの偏り具合を可視化するスクリプトを書いたりしていた。この時自分のチームが使っているレポジトリをどう一覧化するかに困っていた。もともとはひとまずスクリプト上でハードコードしていたが、これだとメンテが面倒。

よくよく考えてみると、GitHubのTeamでレポジトリのアクセス権限を管理してるんだから、それを使えばよいのでは?と思って調べてみたら、GraphQL APIを使ったら一発だったのでメモ。

GraphQL APIを使ってGithub Teamが持つレポジトリを取得する

こんなクエリを書いたらいい。org-name1やteam-name1のところは適宜置き換える。

query {
  organization(login: "org-name1") {
    team(slug: "team-name1") {
      repositories(first: 100) {
        nodes {
          nameWithOwner
        }
        pageInfo {
          hasNextPage
          endCursor
        }
      }
    }
  }
}

https://developer.github.com/v4/explorer/ とかで実行すると様子がわかる。以下のような構造のレスポンスが返ってくる。

{
  "data": {
    "organization": {
      "team": {
        "repositories": {
          "nodes": [
            {
              "nameWithOwner": "shibayu36/repo1"
            },
            {
              "nameWithOwner": "shibayu36/repo2"
            },
            {
              "nameWithOwner": "shibayu36/repo3"
            },
          ],
          "pageInfo": {
            "hasNextPage": false,
            "endCursor": "Y3Vyc29yOnYyOpHOEkFhXQ=="
          }
        }
      }
    }
  }
}

TypeScriptを使ってorg名team名から全レポジトリ一覧を取得する関数を書いてみる

ページネーションもちゃんとして全リストを取ってくるようにしたTypeScriptの関数の例。

import { gql, GraphQLClient } from "graphql-request";

type _TeamRepositoriesResponse = Readonly<{
  organization?: {
    team?: {
      repositories?: {
        nodes: {
          nameWithOwner: string;
        }[];
        pageInfo: {
          hasNextPage: boolean;
          endCursor: string;
        };
      };
    };
  };
}>;
/**
 * チームのレポジトリ名一覧を取得する
 */
export async function fetchTeamRepositories(
  client: GraphQLClient,
  orgName: string,
  teamName: string
): Promise<string[]> {
  const gqlQuery = gql`
    query($orgName: String!, $teamName: String!, $after: String) {
      organization(login: $orgName) {
        team(slug: $teamName) {
          repositories(first: 100, after: $after) {
            nodes {
              nameWithOwner
            }
            pageInfo {
              hasNextPage
              endCursor
            }
          }
        }
      }
    }
  `;

  let after: string | undefined;
  let repos: string[] = [];
  while (true) {
    const data = await client.request<_TeamRepositoriesResponse>(gqlQuery, {
      orgName,
      teamName,
      after,
    });
    const repositoriesNode = data.organization?.team?.repositories;
    if (!repositoriesNode) break;

    repos = repos.concat(repositoriesNode.nodes.map(n => n.nameWithOwner));

    if (!repositoriesNode.pageInfo.hasNextPage) break;

    after = repositoriesNode.pageInfo.endCursor;
  }

  return repos;
}

こんな感じで使える。

const GITHUB_GRAPHQL_ENDPOINT = "https://api.github.com/graphql";
const GITHUB_TOKEN = process.env.GITHUB_TOKEN;
const graphQLClient = new GraphQLClient(GITHUB_GRAPHQL_ENDPOINT, {
  headers: {
    authorization: `Bearer ${GITHUB_TOKEN}`,
  },
});
const teamRepos = await fetchTeamRepositories(graphQLClient, "shibayu36", "team1");

まとめ

GraphQL APIを使って、特定のGitHub Teamが持つレポジトリ一覧を一発で取得するのを試してみた。最近GitHubAPIを色々使ってみてるけど、とにかくGraphQL APIは一発で取りやすいし、API Limitにも引っかかりにくいしで超便利。