【GitHub Actions】Markdown 執筆に textlintの自動校正を取り入れる
背景
GitHub で Markdown 執筆活動
複数人で協力してドキュメントを執筆しています。 執筆の流れは下図のとおり。
執筆ブランチ
を作成する執筆ブランチ
で執筆活動を行うmainブランチ
にマージするための プルリクエスト を作成する- プルリクエスト内でレビュー (必要に応じて修正) を行う
- 最後に
mainブランチ
へマージする (1.
に戻る)
最低限 Git や GitHub 周りの操作を把握できたら、この方法は結構良いと感じています。 以下のようなメリットを享受できています。
- テキストで情報を管理できる ( by Markdown )
- バージョン管理ができる( by Git )
- 文章レビューの流れを統一できる( by GitHub )
textlint で文章校正
さて、執筆活動において より良い文章 を書くために textlint を活用しています。 textlint は 文章校正ツールです。Node.js のパッケージとして提供されています。 公開されている様々なルールを適用してカスタマイズ可能、 「良い文章を書くためのガードレール」として活躍します。
※ textlint についての詳細は以下記事がとても分かりやすいので、御覧ください。
今までは、この textlintを執筆者のローカルPCに導入して、各自使うようにしていました。
やりたいこと
textlint による校正プロセスを自動化 します。 下図のように textlintチェックを プルリクエスト内に統合 させようと思います。
トリガーは「プルリクエスト作成時」および 「その作業ブランチ更新時」です。 そのタイミングで textlint による校正を実行する GitHub Actions ワークフローを実装します。
とりあえず動くモノが欲しい人向け
以下リポジトリに実際に動くモノを載せています。適宜複製して活用ください。
▼ プルリクエスト時の Botコメント例
作ってみる(最小実装版)
#1: textlint 環境の実装
先にローカル環境で textlint実行環境 を構築します。
構築の情報 ( package.json
など)を次ステップの 「ワークフローの実装」で活用します。
前提
Node.js および npmの実行環境構築は省略します。以下バージョンを使っています。
node --version # --> v14.19.1 npm --version # --> 6.14.16
※ Volta というパッケージ管理ツールが便利でした。
npm install で各種パッケージをインストール
npm init
で初期化してから、
以下コマンドで必要なパッケージをインストールします。
npm install --save-dev \ textlint \ textlint-filter-rule-allowlist \ textlint-rule-preset-ja-technical-writing
インストールしたものは以下のとおりです。 ※ルールセットは他にも色々あるので、適宜カスタマイズしてみてください。
- textlint : textlint 本体
- textlint-filter-rule-allowlist : 特定の文字列やパターンを許容するためのフィルター機能
- textlint-rule-preset-ja-technical-writing : 技術文書向けルールセット
この時点で、 package.json
の devDependencies
キーにインストールしたパッケージ情報が記載されているはずです。
{ (..略...) "devDependencies": { "textlint": "^12.1.1", "textlint-filter-rule-allowlist": "^4.0.0", "textlint-rule-preset-ja-technical-writing": "^7.0.0" } }
package.json
および package-lock.json
を GitHubリポジトリへプッシュしておきます。
textlint の初期設定
.textlintrc
を作成します。これは textlint の設定ファイルです。
(書き方のルール詳細は割愛します)
{ "rules": { "preset-ja-technical-writing": true }, "filters": { "allowlist": { "allow": [ "ここに許容される言葉を書いていく" ] } } }
この状態で textlint がローカルで実行できることを確かめます。
echo "テストかもしれない" > ./test.md main npx textlint ./test.md # ${PWD}/test.md # 1:4 error 弱い表現: "かも" が使われています。 ja-technical-writing/ja-no-weak-phrase # 1:9 error 文末が"。"で終わっていません。 ja-technical-writing/ja-no-mixed-period # # ✖ 2 problems (2 errors, 0 warnings)
.textlintrc
を GitHubリポジトリへプッシュしておきます。
#2: ワークフローの実装
.github/workflows/run-textlint-minimum.yml
を作成します。
name: run-textlint-minimum on: pull_request_target: types: [ opened, synchronize ] paths: [ 'contents/**/*.md' ] jobs: run-textlint-minimum: runs-on: ubuntu-latest permissions: pull-requests: write steps: - uses: actions/checkout@v3 with: ref: ${{ github.event.pull_request.head.sha }} - uses: actions/setup-node@v3 with: node-version: 14 - run: npm install - run: npx textlint ./contents/**/*.md >> ./.textlint.log - if: ${{ failure() }} run: gh pr comment --body-file ./.textlint.log "${URL}" env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} URL: ${{ github.event.pull_request.html_url }}
前半部分は下図にざっくり解説を書きました。
各ステップ( steps:
) の内容を解説していきます。
ブランチ切り替え (actions/checkout@v3)
- uses: actions/checkout@v3 with: ref: ${{ github.event.pull_request.head.sha }}
actions/checkout@v3 を使ってブランチを切り替えます。
今回はレビューを行う作業ブランチに切り替えたいので、
パラメータを指定します。 ( この example どおりに ref: $...
部分を書いています )
Node.jsセットアップ (actions/setup-node@v3)
- uses: actions/setup-node@v3 with: node-version: 14
actions/setup-node@v3 を使って Node.js 環境をセットアップします。 パラメータとして「先程のローカル環境の Node.jsのバージョン(14)」を指定しています。
パッケージインストール (npm install)
- run: npm install
このコマンドで、 package.json
記載の
Node.js パッケージをインストールします。
textlint実行 (npx textlint ...)
- run: npx textlint ./contents/**/*.md >> ./.textlint.log
.textlintrc
の設定で textlint を実行します。
contents
ディレクトリ配下の mdファイルがチェック対象です。
実行ログを .textlint.log
に格納します。
校正内容をプルリクエスト上にコメント (gh pr comment ...)
- if: ${{ failure() }} run: gh pr comment --body-file ./.textlint.log "${URL}" env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} URL: ${{ github.event.pull_request.html_url }}
前ステップで校正ルールに引っかかったときは、 そのステップは失敗扱いになります 。
そのため「失敗したときのみ実行する」条件を記載しています ( if: ${{ failure() }}
部分)。
コメント送信は GitHub CLI : gh
を使って実現しています。
gh
コマンドは ubuntu-latest Runner にデフォルトで入っています。
gh
コマンドで必要な情報、つまり「プルリクエストのURL」と「GitHubアクセスの認証情報」を
環境変数 (env:
)に指定します。
[2023-08-10 追記]
textlint側の設定 で エラーにならない(status=0)ように設定できます。それを使ってエラー回避するのが良さそうです。
確認する
以下コマンドを流して、リモートリポジトリへ作業ブランチをプッシュします。
# 作業ブランチの作成 git branch --create write-xxx-section # 作業ブランチに移動 git switch write-xxx-section # 執筆活動 echo "# テスト\nこれはテストかもしれない。\nこんにちは" > ./contents/XX_xxx.md # 変更をコミット git add ./contents/XX_xxx.md git commit -m "xxx章記載" # リモートリポジトリへプッシュ git push origin write-xxx-section
GitHubリポジトリ上でプルリクエストを作成してみます。
しばらくすると以下のようなコメントが飛んできました。
指摘を受けてファイルを修正、再度プッシュします。
…チェックに合格したので指摘コメントは飛んできませんでした (めでたく良い文章になりました)。
ワークフローを改善する
行数は増えますが、最小実装版からいくつか改善したものを作りました。
name: run-textlint on: pull_request_target: types: [ opened, synchronize ] paths: [ 'contents/**/*.md' ] jobs: run-textlint: runs-on: ubuntu-latest permissions: pull-requests: write steps: - name: Switch to pull request branch uses: actions/checkout@v3 with: ref: ${{ github.event.pull_request.head.sha }} - name: Setup node with cache uses: actions/setup-node@v3 with: node-version: 14 cache: 'npm' - name: Install packages via packages.json run: npm install - name: Run textlint (avoiding error) run: npx textlint ./contents/**/*.md -o ./.textlint.log | true shell: bash {0} - name: Report if textlint finds problems run: | if [ -e ./.textlint.log ]; then # create body file pwd_esc=$(pwd | sed 's/\//\\\//g') cat ./.textlint.log | sed "s/${pwd_esc}/### :policeman: ./g" >> ./.body.txt # pr comment gh pr comment --body-file ./.body.txt "${URL}" fi env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} URL: ${{ github.event.pull_request.html_url }}
以降で改善した点を書いていきます。
キャッシュの利用
- name: Setup node with cache uses: actions/setup-node@v3 with: node-version: 14 cache: 'npm'
actions/setup-node の Caching npm dependencies:
を参考にしました。
依存関係をキャッシュして実行時間を減らす試みです。
(ジョブのログを見ると、たしかにキャッシュを活用しているように見受けられます。 が、そこまで全体として大きく実行時間が減った感じはしませんでした…。 もっと時間削減できる工夫があればご指摘ください!)
エラーの回避
- name: Run textlint (avoiding error) run: npx textlint ./contents/**/*.md -o ./.textlint.log | true shell: bash {0}
一言でいうと コマンドでエラーを出さない ようにしています。 この対応を行う理由は 『setup/node のキャッシュ仕様』にあります。 このステップでエラーになると 『setup/node はキャッシュ保存をしてくれない』ためです。
▼ run 部分の解説
まず、前提として textlint は指摘があった際はエラー (終了ステータスが 0
以外)となります。
# ### 指摘が無い場合 echo "こんにちは。" | npx textlint --stdin -f compact # --> (出力無し) echo $? # --> 0 # ### 指摘がある場合 echo "こんにちは" | npx textlint --stdin -f compact # <text>: line 1, col 5, Error - 文末が"。"で終わっていません。 (ja-technical-writing/ja-no-mixed-period) # # 1 problem echo $? # --> 1
npx textlint ... | true
とすることで textlint の指摘の有無に関わらず
終了ステータスを 0
とします。
echo "こんにちは" | npx textlint --stdin -f compact | true echo $? # --> 0
▼ shell 部分の解説
先程の終了ステータスをいじるだけでは、このステップは成功状態になりません。
GitHub Actions デフォルトの bash は
-e
および -o pipefail
オプションが付いた状態で実行されるためです。
-e
オプションは 『エラーが出たところでスクリプトを中断する』ことを意味します。
-o pipefail
オプション は『パイプラインのどこかで失敗した場合は、 コマンド全体を失敗とする 』ものです。
-o pipefail
があるため、 npx textlint ... | true
と書いていても
終了ステータスが 0
とは限りません。
なので 「GitHub Actions デフォルトの bash」 を使わないようにします。
その設定が shell: bash {0}
です。
出力の体裁調整
- name: Report if textlint finds problems run: | if [ -e ./.textlint.log ]; then # create body file pwd_esc=$(pwd | sed 's/\//\\\//g') cat ./.textlint.log | sed "s/${pwd_esc}/### :policeman: ./g" >> ./.body.txt # pr comment gh pr comment --body-file ./.body.txt "${URL}" fi env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} URL: ${{ github.event.pull_request.html_url }}
これは調整後の出力を見ていただければ把握できると思います。 以下のような出力にしました。
ファイルパスを必要最小限 (=ホームディレクトリは削除) にしています。 さらに警察官絵文字を書いてアクセントを加えてみました。
警察官絵文字は置いておいて、多少は見やすくなったと思います。
おわりに
これまでの実装は以下リポジトリにまとめています。 適宜複製して活用ください。
参考
- textlint
- GitHub Docs
- Events that trigger workflows | GitHub Docs : ワークフローのトリガーの調査
- Context | GitHub Docs : 利用できる変数の調査
- Automatic token authentication #Permissions for the GITHUB_TOKEN | GitHub Docs : gh コマンドの権限周りの調査
- Workflow syntax #Exit codes and error action preference | GitHub Docs : エラー回避周りの調査
- Workflow syntax #Custom shell : エラー回避周りの調査
- GitHub Actions
- Bash