Astroブログのタグ・著者の参照切れをCIで検出する

村井 謙太

代表取締役

村井 謙太

Astroブログのタグ・著者の参照切れをCIで検出する

このブログは Astro の content collections で記事を管理しています。記事の frontmatter には、著者とタグをこう書きます。

author: murai-kenta
tags: ["astro", "ci-cd"]

authorcontent/authors/murai-kenta.json、タグは content/tags/<id>.json を指していて、content.config.ts の中で reference() を使ってコレクション同士をつないでいます。

記事を追加していたとき、ふと不安になりました。この author を、murai-kenta のつもりで murai-genta と打ち間違えたらどうなるんだろう、と。型でつながっているように見えるので、ビルドで弾かれて気づけるだろう、と思っていました。

念のため、存在しない著者とタグを書いた記事を1本でっち上げて試してみます。

author: "ghost-author"
tags: ["this-tag-does-not-exist"]

astro checkastro build も、何事もなく通りました。記事ページも普通に生成されます。エラーも警告も出ません。

なぜ通ってしまうのか

ドキュメントを読むと、reference() の挙動が書いてありました。

Validation of referenced entries happens at runtime when using getEntry() or getEntries(): if a referenced entry is invalid, this will return undefined.

reference() が見ているのは「id が文字列か」までで、その id が実在するかどうかは確認していません。実在は getEntry() で取り出すときに初めて分かり、見つからなければ例外ではなく undefined が返ってくるだけです。

記事ページ側のコードは、その undefined を素直に受け流していました。

const author = await getEntry(article.data.author);
const tags = (await Promise.all(article.data.tags.map((t) => getEntry(t))))
  .filter((t) => t != null);

著者が undefined なら、著者欄は空のまま表示されます。タグは filter で落とされるので、打ち間違えたタグはページから消えます。どちらも見た目が少し寂しくなるだけで、ビルドは成功扱いのままです。

挙動としては合理的だと思います。参照先がまだ無い状態でも開発を止めない、という割り切りなのでしょう。ただ、記事が増えてくると、著者欄が空のまま公開されていても自分では気づきにくくなります。

実在チェックを自分で足す

調べたところ、Astro には参照の実在を強制するオプションは見当たりませんでした。なので簡単なチェックを自前で書きます。やることは「全記事の authortags の id が、content/authorscontent/tags に実在するか」を突き合わせるだけです。frontmatter の読み取りには、もともと依存に入っていた gray-matter を使いました。

const { data } = matter(fs.readFileSync(mdPath, "utf-8"));
if (!authorIds.has(String(data.author))) {
  errors.push(`${slug}: author "${data.author}" が存在しません`);
}
for (const tag of data.tags ?? []) {
  if (!tagIds.has(String(tag))) {
    errors.push(`${slug}: tag "${tag}" が存在しません`);
  }
}
// errors があれば最後に process.exit(1)

このブログには元から簡単な検証スクリプト群があったので、その一つとして足して、PRのCIで動くようにしました。さきほどの捨て記事で実行すると、こう出ます。

✗ 参照切れ 2件:
  __ref_test: author "ghost-author" が content/authors/ に存在しません
  __ref_test: tag "nope-not-real" が content/tags/ に存在しません

既存の記事はすべて素通りで、間違っているものだけが指摘されます。これで、著者IDを打ち間違えたまま公開してしまう事故は防げるようになりました。

まとめ

Astro の content collections で reference() を使うときは、「型はつながっているが、参照先の実在まではビルドで保証されない」と覚えておくと安全です。特に著者やカテゴリのような「あって当たり前」の参照ほど、typo が静かに通り抜けます。

実在チェック自体は数十行で書けるので、記事が増えていくブログなら、早めに一つ用意しておくと後が楽です。

記事を書いた人

村井 謙太

代表取締役

村井 謙太

Twitter

東京大学在学中にプログラミング学習サービスのProgateを立ち上げ、CTOとしてプロダクト開発に従事。 Progate退任後に株式会社Anycloudを立ち上げ、現在は多数のクライアントの技術支援を行っている。