人は愚かなので、自分のサイトに更新履歴という項目を作ったとしても、必ずしも毎回更新履歴をupdateできるとは限らない。
ついうっかり忘れてしまう、もしくは面倒でやらないといったことは往々にしてあるだろう。 自分もそのうちの一人になるだろうことが容易に予想できたので、どうせなら自動化してしまおう。
最初は、Astroで更新履歴を自動生成してみたという記事を参考に、Gitのコミットから生成しようとしていたが、せっかくGitHubを使っているのでPull Request駆動で生成するようにしてみた。
アクセストークンを発行する
Pull Requestの情報を得るにはGitHub Rest APIを使う。 このAPIを使うには、あらかじめアクセストークンを発行しておく必要がある。今回はFine-grained personal access tokenを使うことにする。ここから発行画面に飛ぶ。
Repository access→Only select repositoriesを選択し、Pull Requestを取得したいリポジトリを指定する。

そして、Permissions→Repository permissions→Pull requestsをAccess: Read-onlyに設定する。(このとき、Metadataも自動的にRead-onlyになる。)

トークンは.envに追加しておく。
GITHUB_ACCESS_TOKEN=[取得したトークン]GitHub APIをしばく
トークンが発行できたら、APIを叩いていく。OctokitというSDKが用意されているのでインストールする。
npm install @octokit/coreAPIを呼び出すにはoctokit.requestを使う。第一引数に使いたいAPI、第二引数にAPIに渡す引数をオブジェクトで指定する。
const octokit = new Octokit({ auth: import.meta.env.GITHUB_ACCESS_TOKEN });
let response = await octokit.request("GET /repos/{owner}/{repo}/pulls", { owner: "OWNER", repo: "REPO", headers: { "X-GitHub-Api-Version": "2022-11-28", }, state: "all", base: "main",});state引数にallを指定することで既にクローズされたPull Requestも取得している。
また、base引数でmainをベースブランチとするPull Requestのみにフィルターしている。
全てのPull Requestを取得する
GitHub Rest APIではPull Requestの数が多い場合ページごとに分けて返す仕様になっているので、全てのPull Requestを取得するには少し工夫が必要である。
const octokit = new Octokit({ auth: import.meta.env.GITHUB_ACCESS_TOKEN });
let response;let page = 1;const LAST_PAGE_PATTERN = /rel="last"/;
do { response = await octokit.request("GET /repos/{owner}/{repo}/pulls", { owner: "OWNER", repo: "REPO", headers: { "X-GitHub-Api-Version": "2022-11-28", }, state: "all", base: "main", per_page: 100, //1ページ当たりのPull Requestの数(MAX:100) page: page++, });
//responseに対する処理が入る} while (LAST_PAGE_PATTERN.test(response.headers.link as string));response.headers.linkには次のような文字列が入っている。
<https://api.github.com/repositories/000000000/pulls?state=all&base=main&per_page=1&page=1>; rel="prev", <https://api.github.com/repositories/000000000/pulls?state=all&base=main&per_page=1&page=3>; rel="next", <https://api.github.com/repositories/000000000/pulls?state=all&base=main&per_page=1&page=5>; rel="last", <https://api.github.com/repositories/000000000/pulls?state=all&base=main&per_page=1&page=1>; rel="first"
これは、前、次、最初、最後のページへのURLをまとめたものである。ただし、今のページで最後だった場合は最後のページへのURLは含まれない。
これを利用して、response.headers.linkにrel="last" が含まれるかどうかで最後に到達したかを判定している。(もっといい方法がありそうな気もするが…)
フィルターして整理
次に、取得したresponseからマージ済みのものをフィルターする。item.merged_atはマージされた日時をISO形式で表すが、マージされていなければnullなのでこれを使ってマージされたかどうか判定する。
type HistoryItem = { date: Date; title: string;};
const historyItems: HistoryItem[] = new Array(0);
response.data.forEach((item) => { if (item.merged_at) { historyItems.push({ date: new Date(item.merged_at), title: item.title, }); }});最後に、マージされた日時が新しい順になるようソートする。
historyItems.sort((a, b) => { return b.date.getTime() - a.date.getTime();});完成
import { Octokit } from "@octokit/core";
const octokit = new Octokit({ auth: import.meta.env.GITHUB_ACCESS_TOKEN });
type HistoryItem = { date: Date; title: string;};
const historyItems: HistoryItem[] = new Array(0);const LAST_PAGE_PATTERN = /rel="last"/;
let response;let page = 1;
do { response = await octokit.request("GET /repos/{owner}/{repo}/pulls", { owner: "OWNER", repo: "REPO", headers: { "X-GitHub-Api-Version": "2022-11-28", }, state: "all", base: "main", per_page: 100, page: page++, });
response.data.forEach((item) => { if (item.merged_at) { historyItems.push({ date: new Date(item.merged_at), title: item.title, }); } });} while (LAST_PAGE_PATTERN.test(response.headers.link as string));
historyItems.sort((a, b) => { return b.date.getTime() - a.date.getTime();});
export const histories = historyItems;---//...略import HistoryItem from "../HistoryItem.astro";import { histories } from "../../utils/historyGenerator";//...略---
<ul> { histories.map((item) => ( <li> <HistoryItem date={item.date}>{item.title}</HistoryItem> </li> )) }</ul>自動化ってすばらしい。