記事の更新履歴を表示してみる

以前nuxt/content の記事の日付を Git ベースにするという記事で、Generateする際に Git のログを使って記事の日付を管理するようにしました。

この際に使用していた SpawnSync というのは、OSコマンドをNode.jsから実行するためのものです。(Syncとついているほうは同期的で、ついてない非同期のものもあります)

つまり、前回のようにコミットの日付だけでなく、コミットメッセージとかハッシュなんかもとれるんじゃないか、と。
差分までとる必要はないかもしれないですが、いつアップデートしたか、ログツリーを見せられたらおもしろそうだな、ということでやってみることにしました。

content.ts の拡張

前回の /server/plugins/content.ts に追記します。
場所は、前回の updatedAt 用の try - catch のあととかでいいんじゃないでしょうか。

例によってエラーハンドリングは割愛します。

try {
  document.commitHistory = spawnSync(
    'git',
    ['log', '--format=%ct-:-:-%s', path.basename(filePath)],
    { cwd: path.dirname(filePath) }
  ).stdout.toString('utf-8')
} catch (e) { /* todo: add handling */ }

%ctは前回と同じ、UNIXタイムスタンプによるコミット日時です。
%sは件名です。Subjectかな?
-:-:-はあとで処理しやすいようにするための区切り文字です。
あとで String.prototype.split で分割する際に separator に指定しますので、ここでしか使わない特別な記号の並びとかにしておくと良いとおもいます。

これで useContent コンポーザブルで page を取得すると、新たに commitHistory というキーでコミットログが書き込まれるようになります。

で、その page.commitHistory はこんな感じになります。

1683604318-:-:-update post 2023041701\n1681691984-:-:-add post 2023041701\n

\nは改行コードですね。git log したときにコミットごとに改行がはいってるのでそれです。
なので、まずはこれを使って分割すると、1コミットごとの Array になるので、その中でさらに先ほどの区切り文字で分割してあげることで日付とメッセージに分割することができます。

表示用コンポーネントの作成

適当なコンポーネントをつくって page.commitHistory を扱いやすい形に整えます。

const h = ref()
const tmp: { date: string; msg: string; }[] = []
  history.split('\n').forEach((line) => {
    if (line !== '') {
      const log = line.split('-:-:-')
      tmp.push({
        date: dayjs(parseFloat(log[0]) * 1000).format(),
        msg: log[1]
      })
    }
  })
  h.value = tmp

この時点で h.value はこうなるはずです

const h = [
  {
    "date": "2023-05-09T12:51:58+09:00",
    "msg": "update post 2023041701"
  },
  {
    "date": "2023-04-17T09:39:44+09:00",
    "msg": "add post 2023041701"
  }
]

なので、あとはこれを v-for で回すコンポーネントを作れば完成です。

ログがとれない、修正したのにフォーマットが変わらない、等

Nitro プラグイン側での処理はキャッシュされるようで、フォーマットを変更した場合とかにうまく反映されない場合があります。
そういう場合は、以下のコマンドでキャッシュをクリアすることで解消できるはずです。

npx nuxi cleanup

dev server を起動したまま実行すると 500 エラーになりますが、 dev server を立ち上げ直してリロードすれば治るはずです。

応用

たとえば、これを使って、コミットログがない状態のときは _draft の値を true に上書きするようにすれば、各記事の中でドラフトフラグを使わないでもコミットされた記事のみが公開されるようにすることもできそうです。

      try {
        document.commitHistory = spawnSync(
          'git',
          ['log', '--format=%ct-:-:-%s', path.basename(filePath)],
          { cwd: path.dirname(filePath) }
        ).stdout.toString('utf-8')
+       if (document.commitHistory.length === 0) {
+         document.untracked = true
+         document._draft = true
+       } else {
+         document.untracked = false
+       }
      } catch (e) { /* todo: add handling */ }

たとえば上記の場合だと、コミットがない場合1は強制的に _draft の値を true に書き換えます。
なので、generate するときに除外したり、開発モードで[DRAFT]表示をさせたりできます。

もっとも、GitHub Actions 等を使ってオンラインで generate するのであればコミットしてない下書き投稿は push されないため、記事HTMLの生成されないはずなのでそこまで気にしなくていい気もしますが。
ローカルから generate する場合はうっかり公開とかを防げるかもしれませんね。

あと、ついでに untracked というカスタムフラグもつけてみました。
通常は_draft フラグと食い合うのであまり意味がないんですが、別々に取得しておくことで、
公開後に非公開にしたつまり、コミット履歴があるけど非公開にするために _draft を markdown ファイル側に手動で付け加えた記事がわかるようになりますので、まぁいつか役に立つかもしれません。

Footnotes

  1. git log コマンドは履歴がないと空文字になるのでそれを利用してコミット済みかを判定しています

git log --format=%ct:%s

:add 2023061901.md