🎛️

GitHub IssuesとGitHub DiscussionsをMarkdownで出力するCLIコマンドをつくってみた

2024/08/02に公開

はじめに

GitHub Issuesで作成した複数のIssueたちを一括でエクスポートして、GenAI(生成AI)にコンテキストとしてくわせてなにかしたいというときに何かと面倒だったのでCLIコマンド化してみました。

やりたいこと

とあるIssueに別のIssueが紐付いているような状況で、再帰的に取得を繰り返してMarkdownとして出力するスクリプトです。
Issue内にGitHub Discussionsが紐付いている場合も取得できるようにしています。

ローカルで試した出力した結果です。
issue1にissue2とissue3が紐付いている例で、ちゃんとMarkdownとして出力されています。

環境情報

  • node 20.15.1
  • npm 10.8.2
  • typescript 5.5.3- @inquirer/prompts 5.1.3
  • @octokit/rest 21.0.1

ソースコード

https://github.com/drumnistnakano/github-output-issues-and-discussions-fetcher

実行手順

  1. リポジトリをクローンします:

    git clone https://github.com/drumnistnakano/github-output-issues-and-discussions-fetcher.git
    cd github-output-issues-and-discussions-fetcher
    
  2. 依存関係をインストールします:

    npm install
    
  3. ルートディレクトリに .env ファイルを作成し、GitHubトークンを追加します:

    GITHUB_TOKEN=your_github_token
    

    このとき、GitHubのトークン用の設定は以下のようにしました。
    GitHubへアクセスする際の認証としてPersonal Access Tokenを使ってます。
    プライベートリポジトリへの読み込みアクセスが必要なので、以下のようなアクセス許可を設定しました。
    Discussionsにもアクセスするので、必要な部分にチェックをいれています。
    チェックが終わって登録するとトークンを生成するので、控えておきましょう。

  4. CLIツールを実行します:

    npm run cli
    

    プロンプトに従って、リポジトリのオーナー、リポジトリ名、取得したいIssue番号を入力します。

    npm run cli
    
     > cli
     > npm run start ./main.ts
     
     
     > start
     > npx tsx ./main.ts
     
     ? Enter the repository owner: drumnistnakano
     ? Enter the repository name: github-output-issues-and-discussions-fetcher
     ? Select the task to execute: Fetch Issue and Related
     ? Do you want to execute the task? controller=fetchIssueAndRelated yes
     ? Enter the issue numbers to fetch (comma-separated): 1
    

解説

@octokit/rest

GitHub APIをラッパしてAPIクライアントを利用して、Issueの情報を取得します。
@octokit/restというライブラリがGitHub公式から公開されているので、これを利用しました。

GitHub Discussionの取得

@octokit/rest v21.0.1には、ディスカッションを取得するためのメソッドが現時点で存在しないため(あったらすみません)、GraphQL APIを使用する必要があります。

GraphQL APIは、ディスカッションを取得するためのクエリを提供しており、これを利用することでディスカッションの情報を取得できました。

fetchDiscussion.ts
  const query = `
    query {
      repository(owner: "${repoOwner}", name: "${repoName}") {
        discussion(number: ${discussionNumber}) {
          id
          title
          body
          comments(first: 100) {
            nodes {
              author {
                login
              }
              body
            }
          }
        }
      }
    }
  `;

inquirer.js

inquirerというライブラリを使ってインタラクティブなカスタムCLIを作れます。
inquirerがv10.0.0を最近リリースしたようで、@inquirer/promptsへ移行する旨の記載があったので、@inquirer/promptsをインストールしました。

https://github.com/SBoudrias/Inquirer.js/releases/tag/inquirer%4010.0.0

@inquirer/promptsのselect, confirm, inputを利用しています。
selectで選択肢をつくれます。今回は一つしかないですが、もし今後実装したいユースケースが増えたら、追加したいと思っています。
confirmはコマンドを実行許可を作れます。Yes or Noの形式で答えて結果によって実行を進めるかをプロンプトで表現できます。
inputはプロンプトに標準入力したテキストを使って次の処理で使えます。入力した値をもとにカンマ区切りのIssue番号を入力できるようにしています。

main.ts
  const controllerAnswers = await select({
    message: "Select the task to execute:",
    choices: controllersChoices,
  });
  const controller = controllerAnswers;

  const isConfirmed = await confirm({
    message: `Do you want to execute the task? controller=${controller}`,
  });

  if (!isConfirmed) {
    return;
  }

  switch (controller) {
    case "fetchIssueAndRelated": {
      const issueNumbersInput = await input({
        message: "Enter the issue numbers to fetch (comma-separated):",
        required: true,
      });
      const issueNumbers = issueNumbersInput
        .split(",")
        .map((num) => Number.parseInt(num.trim()));
      await fetchIssueAndRelated({
        repoOwner,
        repoName,
        issueNumbers,
        outputDir,
        octokit,
      });
      break;
    }
    default: {
      throw new Error(`Invalid controller. controller=${controller}`);
    }

おわりに

用途としてはかなり限定されてますが、誰かの役に立てれば幸いです。

参考

https://octokit.github.io/rest.js/v20

https://github.com/SBoudrias/Inquirer.js

https://docs.github.com/ja/enterprise-server@3.13/graphql/guides/using-the-graphql-api-for-discussions

Discussion