はじめに
最近社内で公式なドキュメントを管理する仕組みを作っていて、GitHub Pagesを使っています。
ドキュメントを編集する際にGitHubのPRを提出しています。
ドキュメントの編集するための開発環境があり、Docker内であればドキュメントのチェックをされる仕組みが存在しています。
ただし、開発環境のセットアップなしでドキュメントが編集されたら、ドキュメントは汚れてしまう恐れがあります。
そのためにGitHub Actionsを使って、PR作成時ドキュメントをチェックする仕組みを作ろうとしました。
初めてGitHub Actionsを使ったというのもあり、勉強になったことなど記録するようにします。
チェックの内容
現時点で以下のチェックをしています。もし今後新しいチェックなど追加あり、勉強になったことがあれば、
この記事にどんどん追加します。
- ファイルの最後に新しい行があるか
- 改行コードはLFなのか
- markdownlintでのチェック
- textlintでのチェック
- cspellでのチェック
ファイルの最後に新しい行があるか
以下の記事によれば、ファイルの最終行に改行がない場合、修正と無関係の差分がpull request上に出て、レビュアーの負荷になる場合があります。
Dockerイメージ内の開発はVscodeを使っています。「Files: Insert Final Newline」にチェックを入れています。
一律最終行に改行あることを統一したいため、チェックを入れました。
改行コードはLFなのか
Dockerイメージの中でドキュメントの編集をしていれば、改行コードはLFになります。ただ、普段開発する際には基本的にWindowsを使っているため、その環境でファイルを編集するとファイルが意図せずCRLFになってしまうことがあります。
そのためにチェックを追加しました。
markdownlintでのチェック
ドキュメントは全部markdownで書かれています。マークダウンの記法に合っているのかをチェックするため、
markdownlintを使っています。
textlintでのチェック
textlintとはLintと呼ばれる静的解析ツールで、テキストファイル(.txt)やMarkdownファイル(.md)等を対象に、校正ルールにもとづいて文章校正を行うツールです。
今回はマークダウンをチェックするためにtextlintを使っています。
cspellでのチェック
cspellはスペルミスをチェックするツールです。日本語のチェックはできませんが、typescriptのソースの中のスペルミスをチェックするなどのため使っています。
チェック方法
Github Actions
Github Actionsの内容はは以下です。
on: [pull_request]
jobs:
file-check:
name: runner / file-check
runs-on: ubuntu-latest
steps:
- name: Checnout base branch
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.base.ref }}
- name: Checkout Pull Request
uses: actions/checkout@v4
- name: Check final new line exists
run: bash ./.github/scripts/check-newline.sh
- name: Check EOL is lf
run: bash ./.github/scripts/check-lf.sh
- name: Run yarn install
run: yarn
- name: Run markdownlint
run: |
echo "::add-matcher::.github/problem_matcher/markdownlint-matcher.json"
yarn lint:md
continue-on-error: true
- name: Run textlint
run: |
echo "::add-matcher::.github/problem_matcher/textlint-matcher.json"
yarn lint:text
continue-on-error: true
- name: Run cspell
run: |
echo "::add-matcher::.github/problem_matcher/cspell-matcher.json"
yarn cspell
Pull requestがあったらリポジトリをチェックアウトし、順番通りに各チェックしていくイメージです。
そして、静的チェックはエラーがあっても処理が継続できるようにcontinue-on-error
のオプションを使いました。
ファイルの最後に新しい行があるかのチェック方法
以下のshellコマンドを、Github Actionsから実行するようにしました。
#!/bin/bash
set -eux
git config --local core.quotepath false
no_check_extension_array=("jpeg" "jpg" "png" "svg" "ico")
git diff --diff-filter=d --name-only origin/${GITHUB_BASE_REF}..${GITHUB_SHA} |
while read -r file; do
extension="${file##*.}"
if printf '%s\n' "${no_check_extension_array[@]}" | grep -qx "$extension"; then
continue
fi
if [ -n "$(tail -c 1 "$file")" ]; then
echo "[ERROR] File $file has no newline at the end of file"
exit 1
fi
done
git diffでbaseブランチとの修正差分を取得し、確認します。
テキストファイルのみ最終行は改行なのかチェックします。
(テキストファイルの種類がかなり多いため、テキストファイルファイルではないものを除外するようにしました。)
ドキュメントのファイルパスに日本語が含まれることquoteされて、github actionsの実行時ファイルが見つかりません。
そのためこの一行でquoteしないように設定を変えています。
Check Out
Github Actions上、リポジトリをcheckoutし、利用するのは以下になると思います。
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
ただし、この方法だとコミットが増えてくると遅くなる大きなデメリットがあるため、あまりいい方法ではありません。別の方法があるのか試しました。
checkout時、github.event.pull_request.base.shaとgithub.event.pull_request.head.shaをチェックし、git diffで該当のshaチェックするのを試したのですが、意図しない差分が入るため結果的に使いませんでした。
結果的には以下の記事を参考に実装しました。
github.event.pull_request.base.ref
とGITHUB_BASE_REF
、github.sha
とGITHUB_SHA
は同じハッシュ値であるのを確認しています。
変数名 | 説明 |
---|---|
GITHUB_BASE_REF | ワークフローの実行における base ref の名前または pull request のターゲット ブランチ。 これは、ワークフローの実行をトリガーするイベントが pull_request か pull_request_target である場合にのみ設定されます。 たとえば、main のようにします。 |
GITHUB_SHA | ワークフローをトリガーしたコミット SHA。 このコミット SHA の値は、ワークフローをトリガーしたイベントによって異なります。 |
Github Actionsの実装的にもbaseブランチしか追加で取得していないです。
- name: Checnout base branch
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.base.ref }}
ファイルパス
shellコマンドの最初のところで以下のコマンドを使っています。
git config --local core.quotepath false
git diff
本体のgitコマンドはこちらです。
git diff --diff-filter=d --name-only origin/${GITHUB_BASE_REF}..${GITHUB_SHA}
削除されたファイルはチェックしなくていいですので、--diff-filter=dオプションを使ったコマンドの結果から除外しています。
実際にチェックしたいのは差分のファイルだけですので、--name-onlyオプションを使って、変更されたファイル名だけ表示するようにしています。
改行コードはLFなのかのチェック方法
ファイルの最後に新しい行があるかのチェックと同様に、shellコマンドを使ってチェックしています。
以下の通りです。
#!/bin/bash
set -eux
git config --local core.quotepath false
no_check_extension_array=("jpeg" "jpg" "png" "svg" "ico")
git diff --diff-filter=d --name-only origin/${GITHUB_BASE_REF}..${GITHUB_SHA} |
while read -r file; do
extension="${file##*.}"
if printf '%s\n' "${no_check_extension_array[@]}" | grep -qx "$extension"; then
continue
fi
if [ "$(awk -v RS='\r\n' 'END{print NR}' "$file")" -ne 1 ]; then
echo "[ERROR] Please commit $file with LF instead of CRLF!"
exit 1
fi
done
git diffでbaseブランチとの修正差分を取得し、確認します。
テキストファイルのみ改行コードがLFなのかチェックします。
その他、テキストファイル以外はLF固定にするため、.gitattributes
というファイルも追加しました。
リポジトリに含まれる可能性のあるテキストファイルではないものは除外しています。
# Set the default behavior, in case people don't have core.autocrlf set.
* text eol=lf
*.jpeg -text
*.jpg -text
*.png -text
*.svg -text
*.ico -text
markdownlintでのチェック方法
実装上以下のものしかないのですが、説明します。
- name: Run yarn install
run: yarn
- name: Run markdownlint
run: |
echo "::add-matcher::.github/problem_matcher/markdownlint-matcher.json"
yarn lint:md
continue-on-error: true
yarn
今のリポジトリ上、yarnを使ってパッケージ管理しています。
yarnについては以下のリンクをご参照ください。(npmと似ている)
markdownlint関連のコマンドはpackage.jsonに定義され、モジュールをインストールするのに
yarn
の一行でできます。
markdownlintのproblem matcher
lintのチェック結果を可視化をするのにGithubのproblem matcherという仕組みがあります。
エラー内容をjsonファイルの定義によってpull requestに表示させることができます。
以下のイメージです。
problem matcherのjsonファイルの定義は以下の通りです。
(実際にログに出るエラーの内容に基づいて、regexpの正規表現の定義を調整するイメージです。)
{
"problemMatcher": [
{
"owner": "markdownlint-matcher",
"pattern": [
{
"regexp": "^([^:]*):(\\d+):?(\\d+)?\\s([\\w-\\/]*\\s.*)$",
"file": 1,
"line": 2,
"column": 3,
"message": 4
}
]
}
]
}
参考記事
yarn lint:md
このコマンドについてはpackage.jsonのscriptsに以下の定義があるためです。
実際にはpackage.jsonに記載されたコマンドが実行されます。
"lint:md": "markdownlint-cli2 'src/**/*.md'"
textlintでのチェック方法
markdownlintと同様に、problem matcherの仕組みを使っています。
textlintのproblem matcher
textlintのproblem matcherのjsonは以下の通りです。
{
"problemMatcher": [
{
"owner": "textlint-matcher",
"severity": "error",
"pattern": [
{
"regexp": "^([^\\s].*)$",
"file": 1
},
{
"regexp": "^\\s+(\\d+):(\\d+)\\s+✓*\\s*(error|warning|info)\\s+(.*)\\s\\s+(.*)$",
"line": 1,
"column": 2,
"severity": 3,
"message": 4,
"code": 5,
"loop": true
}
]
}
]
}
yarn lint:text
package.jsonに以下の定義があり、textlintのチェックが実行されます。
"lint:text": "textlint 'src/**/*.md'"
cspellでのチェック方法
markdownlintと同様に、problem matcherの仕組みを使っています。
cspellのproblem matcher
cspellのproblem matcherのjsonは以下の通りです。
{
"problemMatcher": [
{
"owner": "cspell",
"pattern": [
{
"regexp": "^([^:]*):(\\d+):?(\\d+)?\\s(.*)$",
"file": 1,
"line": 2,
"column": 3,
"message": 4
}
]
}
]
}
yarn cspell
package.jsonに以下の定義があり、cspellのチェックが実行されます。
"cspell": "cspell --no-progress '**/*'"
cspell.json
製品名など、英語として正しくないが、ドキュメントに含まれているため、cspellのチェック対象から除外したい場合があると思います。
その時はcspell.jsonを使います。
cspell.jsonのignoreWords
に入れれば、チェックの対象から除外できます。
ジョブの並列実行
今は複数のコマンドやチェックする内容を一つのジョブにまとめたのですが、
最初そうではなかったです。
当初の思いはチェック内容が違うため、ジョブがわかればわかりやすいと思ったのですが、実際に使う人がエラーの内容さえわかれば別にジョブに一つがいいと思いました。
後、Github actionsは各ジョブが分かれていて、リソースの共有していないです。そのため、ジョブが分かれていれば、各ジョブでのcheckoutとyarnになってしまいます。無駄にnode_modulesをインストールするのがよくありませんので、最終的には一つのジョブにまとめて、continue-on-error
をtrueにしてエラーになっても実行できるようにしました。
少し紹介です。最終的に使ってはいませんが、Github Actionsのジョブマトリックスを試しました。
複数ジョブを同時に実行することができます。
最後に
以上がドキュメントチェックする仕組みになります。
皆さんのご参考になれれば幸いです!