🐟

Zenn 執筆用リンターを整備する - treefmt で一括実行

に公開

はじめに

前回の記事では VSCode と Zenn CLI を利用したローカル執筆環境を紹介しました。

https://zenn.dev/trifolium/articles/007bff63247432

本記事では、リンターの設定構築を紹介します


具体的には、以下の要素をリンターでチェックします。

  • Markdown の体裁(markdownlint-cli2)
  • 日本語表現(textlint)
  • 英単語のスペル(cspell)
  • URL リンク切れ(lychee)

加えて、Zenn に合わせたリンターの設定も紹介します。

また、今回は複数のリンターを用いるので、treefmt でリンターを一括実行する方法も解説します。

想定読者

  • Zenn CLI / VSCode で執筆している方
  • markdownlint や textlint を使っている(使いたい)方
  • 複数のリンターを一括で回したい方

概要

  • treefmt に各リンターを登録し、一括実行
  • 対象は articles/*.md を指定
  • リンターの設定は linter/ に集約

以下の様にリンターでチェックが出来ます

警告がでる文章
文末に。がないし空白がある 

開かない URL。https://zenn.dev/trifolium/articles/007bff6324743
[存在しないセクションへジャンプ](#a)
[Zenn仕様の画像パス(存在しないファイル)](/images/5b01a68b80808b/hoge.webp)

javascript -> x, JavaScript -> o
JavaScrit タイポ。

  • VSCode でリアルタイムにリンターのチェックが入る

URL のチェックだけは CLI 専用にしています

VSCode でのリンターの警告画面

  • CLI でリンターを実行
Bash
treefmt --config-file ./linter/treefmt.toml
長いので折り畳み

上記の文章を対象にリンターを実行しました。

Bash
$ task
ERRO formatter | md[1]: failed to apply with options '[--config linter/.markdownlint-cli2.jsonc]': exit status 1

markdownlint-cli2 v0.18.1 (markdownlint v0.38.0)
Finding: articles/5b01a68b80808b.md
Linting: 1 file(s)
Summary: 2 error(s)
articles/5b01a68b80808b.md:857:14 MD009/no-trailing-spaces Trailing spaces [Expected: 0 or 2; Actual: 1]
articles/5b01a68b80808b.md:860:1 MD051/link-fragments Link fragments should be valid [Context: "[存在しないセクションへジャンプ](#a)"]

ERRO formatter | text[2]: failed to apply with options '[--config linter/.textlintrc.json]': exit status 1


/home/ryu/dev/zenn_contents/articles/5b01a68b80808b.md
  857:13  error    文末が"。"で終わっていません。                          ja-technical-writing/ja-no-mixed-period
  863:1   ✓ error  Incorrect term: “javascript”, use “JavaScript” instead  terminology

✖ 2 problems (2 errors, 0 warnings, 0 infos)1 fixable problem.
Try to run: $ textlint --fix [file]


ERRO formatter | url[3]: failed to apply with options '[--root-dir /home/ryu/dev/zenn_contents --timeout 10 --max-retries 2]': exit status 2

Issues found in 1 input. Find details below.

[articles/5b01a68b80808b.md]:
[ERROR] file:///home/ryu/dev/zenn_contents/images/5b01a68b80808b/hoge.webp | Cannot find file
[404] https://zenn.dev/trifolium/articles/007bff6324743 | Rejected status code (this depends on your "accept" configuration): Not Found

🔍 25 Total (in 0s)23 OK 🚫 2 Errors

ERRO formatter | spell[4]: failed to apply with options '[]': exit status 1

1/1 articles/5b01a68b80808b.md 635.59ms X
articles/5b01a68b80808b.md:864:5 - Unknown word (Scrit) fix: (Scrip, Script)
CSpell: Files checked: 1, Issues found: 1 in 1 file.

traversed 164 files
emitted 1 files for processing
formatted 0 files (0 changed) in 4.32s
Error: failed to finalise formatting: formatting failures detected
task: Failed to run task "default": exit status 1

記事の流れ

  • 各リンターの紹介
  • リンター、treefmt の導入
  • リンターごとの設定
  • treefmt の設定
  • VSCode 拡張の設定

フォルダ構成

フォルダ構成
zenn_contents/
├─ .vscode/
│  ├─ cspell.json  // シンボリックリンク
│  ├─ extensions.json
│  └─ settings.json
├─ articles/
├─ node-pkgs/
│  └─ package.json
├─ linter/
│  ├─ treefmt.toml
│  ├─ .markdownlint-cli2.jsonc
│  ├─ .textlintrc.json
│  └─ cspell.json
└─ flake.nix

1. 使用ツールの紹介

1.1 markdownlint-cli2

Markdown の構文チェックを行います。

  • CLI

https://github.com/DavidAnson/markdownlint-cli2

  • VSCode 拡張

https://marketplace.visualstudio.com/items?itemName=DavidAnson.vscode-markdownlint

1.2 textlint

日本語の表記・文体ルールのチェックを行います。
数多くの拡張が公開されており、目的に合わせたカスタマイズが可能です。

  • CLI

https://github.com/textlint/textlint

  • textlint 拡張

リンターでチェックしない領域を指定できます。

https://github.com/textlint/textlint-filter-rule-comments

日本語周りにおけるスペースの有無を決定するルールプリセットです。

https://github.com/textlint-ja/textlint-rule-preset-ja-spacing

技術文書向けのルールプリセットです。

https://github.com/textlint-ja/textlint-rule-preset-ja-technical-writing

英語の技術文書内の用語、ブランド、テクノロジーのスペルをチェックして修正するためのルールです。

https://github.com/sapegin/textlint-rule-terminology

  • VSCode 拡張

https://marketplace.visualstudio.com/items?itemName=3w36zj6.textlint

1.3 cspell

英単語のスペルチェックを行います。

  • CLI

https://github.com/streetsidesoftware/cspell-cli

  • VSCode

https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.code-spell-checker

1.4 lychee

URL リンク切れのチェックを行います。

  • CLI

https://github.com/lycheeverse/lychee

1.5 treefmt

複数のリンターを一括実行できます。

  • CLI

https://github.com/numtide/treefmt

2. 各ツールの導入

Nix を利用する方法(Zenn CLI 環境として隔離できる、ユーザー環境をクリーンにできるのでお勧め)」と「普通にインストールする方法」を紹介します。

2.1 Nix を利用する方法

2.1.1 前置き

この記事の環境を前提としています。

https://zenn.dev/trifolium/articles/007bff63247432

importNpmLock を用いて、Node パッケージを Nix の devShell で利用可能にします

詳細はこちらの記事で解説しています。

https://zenn.dev/trifolium/articles/6678b0c0fb0d27

2.1.2 Node パッケージ管理用のファイルを作成

プロジェクト直下に node-pkgs フォルダを作成し、package.json を作成します。

フォルダ構成
  zenn_contents/
+ ├─ node-pkgs/
+ │  └─ package.json
  └─ flake.nix
package.json
{
  "name": "zenn-cli-env",
  "version": "1.0.0",
  "private": "true",
  "devDependencies": {
    "cspell": "9.2.1",
    "markdownlint-cli2": "0.18.1",
    "textlint": "15.2.2",
    "textlint-filter-rule-comments": "1.2.2",
    "textlint-rule-preset-ja-spacing": "2.4.3",
    "textlint-rule-preset-ja-technical-writing": "12.0.2",
    "textlint-rule-terminology": "5.2.15",
    "zenn-cli": "0.2.3"
  }
}

2.1.3 flake.nix の作成

flake.nix
{
  description = "Zenn CLI environment";

  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
    flake-utils.url = "github:numtide/flake-utils";
  };

  outputs =
    { nixpkgs, flake-utils, ... }:
    flake-utils.lib.eachDefaultSystem (
      system:
      let
        pkgs = nixpkgs.legacyPackages.${system};
        inherit (pkgs) importNpmLock;
        nodejs = pkgs.nodejs_24;
        npmRoot = ./node-pkgs;
      in
      {
        devShells.default = pkgs.mkShell {
          packages = [
            pkgs.treefmt
            pkgs.lychee
            importNpmLock.hooks.linkNodeModulesHook
          ];
          npmDeps = importNpmLock.buildNodeModules {
            inherit npmRoot nodejs;
          };
        };

        # for updating package.json and package-lock.json
        devShells.node = pkgs.mkShell {
          packages = [
            nodejs
            pkgs.npm-check-updates
          ];
        };
      }
    );
}

2.1.4 パッケージの更新

先ほど package.json に記述したパッケージのバージョンが古いかもしれないので、最新へ更新します。

node-pkgs に移動します。

cd your_path/zenn_contents/node-pkgs

devShells.node 環境を利用して、ncu コマンドを実行します。

nix develop .#node -c ncu -u

https://www.npmjs.com/package/npm-check-updates

2.1.5 package-lock.json の作成

npm を利用して package.json に記載された Node パッケージの依存関係を package-lock.json に記述します。

nix develop .#node -c npm install --package-lock-only

2.1.6 devShell の起動

プロジェクト直下に戻り、nix develop で環境を起動します。
package-lock.json に基づいてパッケージが構築され、プロジェクト直下に node_modules がリンクされます。

cd ..
nix develop

これで準備は完了です

2.2 通常の方法

以下のコマンドを実行してインストールします。

Bash
npm install markdownlint-cli2 textlint textlint-filter-rule-comments textlint-rule-preset-ja-spacing textlint-rule-preset-ja-technical-writing textlint-rule-terminology cspell-cli
パッケージ一覧

コピペ用に置いておきます。

パッケージ
markdownlint-cli2
textlint
textlint-filter-rule-comments
textlint-rule-preset-ja-spacing
textlint-rule-preset-ja-technical-writing
textlint-rule-terminology
cspell-cli
Bash
snap install lychee

treefmt は GitHub からバイナリをダウンロードできます。

https://github.com/numtide/treefmt/releases

3. 各リンターの設定

3.1 markdownlint-cli2

linter フォルダと .markdownlint-cli2.jsonc を作成します。

フォルダ構成
  zenn_contents/
+ └─ linter/
+     └─ .markdownlint-cli2.jsonc
.markdownlint-cli2.jsonc
{
    "config": {
        "MD001": false,
        "MD012": false,
        "MD013": false,
        "MD022": false,
        "MD024": false,
        "MD025": false,
        "MD029": false,
        "MD033": false,
        "MD034": false 
    }
}

ルール一覧(MDxxx)はこちらのページに記載されています。

https://github.com/DavidAnson/markdownlint

無効化したルールと理由

MD001: 見出しレベルは一度に 1 レベルずつ増加します

NG
# Heading 1

### Heading 3
無効化した理由
# 見出し
#### 目次に表示したくない見出し
こう書きたい時がある。

MD012: 連続する複数の空白行

NG
text


text
無効化した理由
# 見出し
解説


# 次の見出し
こう書けた方が編集中に読みやすい。
個人の好み。

MD013: 行の長さ

80 文字を超えると警告が発生する。
textlint と役割が重複しているので、無効化。

MD022: 見出しは空白行で囲む必要があります

NG
# Heading 1
Some text

Some more text
## Heading 2
無効化した理由
# 見出し
こう書けた方が読みやすい。
個人の好み。

MD024: 同じ内容の複数の見出し

NG
# Some text

## Some text
無効化した理由
# 紹介
## textlint

# 使い方
## textlint

こういった書き方をしたいから。

MD025: 同じ文書内に複数のトップレベル見出しがある

NG
# Top level heading

# Another top-level heading
無効化した理由
# はじめに

# 概要

# おわりに

こういった書き方をしたいから。

MD029: 順序付きリスト項目の接頭辞

NG
- 1. まずこれをする
- 2. 次のそれをする
無効化した理由
以下の様な書き方を強制するルールです。

1. Do this.
2. Do that.
3. Done.

これだけなら無害だったのですが、NG 例の書き方でも警告が出ました。

NG の様な書き方は使うので無効化。

MD033: インライン HTML

NG
<h1>Inline HTML heading</h1>
無効化した理由
|||
|--|--|
|改行無し|改行<br>有り|

表の中で改行する際に HTML タグを利用するから。

MD034: 裸の URL が使用される

NG
For more info, visit https://www.example.com/ or email user@example.com.
無効化した理由
https://zenn.dev/zenn/articles/markdown-guide

Zenn 独自の記法にリンクカードがあり、使いたいから。

3.2 textlint

.textlintrc.json を作成します。

フォルダ構成
  zenn_contents/
  └─ linter/
      ├─ .markdownlint-cli2.jsonc
+     └─ .textlintrc.json
.textlintrc.json
{
  "plugins": {},
  "filters": {
    "comments": true
  },
  "rules": {
    "preset-ja-spacing": {
      "ja-space-between-half-and-full-width": {
        "space": "always"
      }
    },
    "preset-ja-technical-writing": {
      "ja-no-weak-phrase": false,
      "no-mix-dearu-desumasu": {
        "preferInList": "ですます"
      },
      "ja-no-mixed-period": {
            "allowPeriodMarks": [":"]
      },
      "no-exclamation-question-mark": false
    },
    "terminology": {
      "exclude": [
        "V[ -]?S[ -]?Code"
      ]
    }
  }
}

追加した拡張

textlint-filter-rule-comments

textlint でチェックしない領域を指定できます。

markdown
<!-- textlint-disable -->

This is ignored text by rule.
Disables all rules between comments

<!-- textlint-enable -->

.textlintrc.json では、この拡張を有効化するだけでカスタマイズはしていません。

.textlintrc.json
{
  "filters": {
    "comments": true
  }
}

textlint-rule-preset-ja-spacing

日本語周りにおけるスペースの有無を決定するルールプリセットです。

デフォルトでは、半角・全角の間にスペースを入れない設定になっています。

私の好みで「英単語や半角数字」と日本語はスペースを入れたいので、入れる設定に変更しています。

.textlintrc.json
{
  "rules": {
    "preset-ja-spacing": {
      "ja-space-between-half-and-full-width": {
        "space": "always"
      }
    }
  }
}

textlint-rule-preset-ja-technical-writing

技術文書向けのルールプリセットです。

下記をカスタマイズしています。

  • ja-no-weak-phrase
    〜と思いますといった弱い表現を禁止します。
    そういった表現を使いたいので、許可に変更しています。

  • no-mix-dearu-desumasu
    見出しは自動、本文はですます調、箇条書きはである調、で統一するルール。
    本文・箇条書きどちらも「ですます調」に統一するように変更しています。

  • ja-no-mixed-period
    「。」のつけ忘れチェックを行うルール。
    例外として許可したい文字列(「。」として認識する文字列)として「:」を追加しています。
    追加しないと、以下の構文で警告が出ます。

markdown
:::message
メッセージをここに。
:::
  • no-exclamation-question-mark
    感嘆符!!、疑問符??を禁止するルール。
    使えた方が便利なので許可に変更しています。
.textlintrc.json
{
  "rules": {
    "preset-ja-technical-writing": {
      "ja-no-weak-phrase": false,
      "no-mix-dearu-desumasu": {
        "preferInList": "ですます"
      },
      "ja-no-mixed-period": {
            "allowPeriodMarks": [":"]
      },
      "no-exclamation-question-mark": false
    }
  }
}

textlint-rule-terminology

英語の技術文書内の用語、ブランド、テクノロジーのスペルをチェックして修正するためのルールです。

NG例
Javascript → JavaScript
NPM → npm
front-end → frontend
website → site
Internet → internet

この拡張は継続的な設定の見直しが必要だと思います

例えば、通常だと VSCode はエラーになります。
(Visual Studio Code 表記にすべき)

textlint のエラー

このエラーを止めたいと仮定して、方法を解説します

まず、公式の GitHub リポジトリで辞書を確認します。

https://github.com/sapegin/textlint-rule-terminology/blob/master/terms.jsonc

Visual Studio Code で検索すると、以下が見つかります。

terms.jsonc
  ["Visual ?Studio ?Code", "Visual Studio Code"],
  ["V[ -]?S[ -]?Code", "Visual Studio Code"],

.textlintrc.jsonrules.terminology.excludeV[ -]?S[ -]?Code を追加します。

これで VSCode は許容されます。
なお、Visual ?Studio ?Code は除外指定していないので、visual studio code はエラーが出ます。

.textlintrc.json
{
  "rules": {
    "terminology": {
      "exclude": [
        "V[ -]?S[ -]?Code"
      ]
    }
  }
}

3.3 cspell

cspell.json を作成します。

フォルダ構成
  zenn_contents/
  └─ linter/
      ├─ .markdownlint-cli2.jsonc
      ├─ .textlintrc.json
+     └─ cspell.json
cspell.json
{
    "version": "0.2",
    "files": [
        "articles/*.md"
    ],
    "words": [
        "Zenn"
    ]
}

辞書登録

固有名詞は cspell の辞書に登録されていないため、警告がでます。

cspell の警告例

cspell.jsonwords に登録したい単語を記述します。

linter/cspell.json
{
    "words": [
        "Zenn"
    ]
}

VSCode の場合

クイックフィックスを使うと簡単に辞書登録できます

Zenn をマウスオーバーして、Add "Zenn" to config: .vscode/cspell.json を押すだけです。

警告画面

クイックフィックス画面

特定の範囲だけ無効化

cspell でチェックしない領域を指定できます。

markdown
<!-- cspell:disable -->

This is ignored text by rule.
Disables all rules between comments

<!-- cspell:enable -->

3.4 lychee

lychee.toml を作成します。

フォルダ構成
  zenn_contents/
  └─ linter/
      ├─ .markdownlint-cli2.jsonc
      ├─ .textlintrc.json
      ├─ cspell.json
+     └─ lychee.toml
lychee.toml
# Maximum number of allowed retries before a link is declared dead.
max_retries = 2

# Website timeout from connect to response finished.
timeout = 10

# Root path to use when checking absolute local links, must be an absolute path
root_dir = "<your_path>/zenn_contents"

root_dir を指定することで、Zenn 独自の画像パスの書き方が正しく認識可能になります。
他の設定はコメントをお読みください。

4. treefmt の設定

treefmt.toml を作成します。

フォルダ構成
  zenn_contents/
  └─ linter/
      ├─ .markdownlint-cli2.jsonc
      ├─ .textlintrc.json
      ├─ cspell.json
+     └─ treefmt.toml
treefmt.toml
[formatter.md]
command = "markdownlint-cli2"
options = ["--config", "linter/.markdownlint-cli2.jsonc"]
includes = ["articles/*.md"]
priority = 1

[formatter.text]
command = "textlint"
options = ["--config", "linter/.textlintrc.json"]
includes = ["articles/*.md"]
priority = 2

[formatter.url]
command = "lychee"
options = ["--config", "linter/lychee.toml"] 
includes = ["articles/*.md"]
priority = 3

[formatter.spell]
command = "cspell"
includes = ["articles/*.md"]
priority = 4

[global]
excludes = [
    "articles/00341ed49b7935.md"
]

設定の解説

リンターごとの基本的な設定

実際の挙動を見た方が理解しやすいかと思います。

以下の様に設定した場合を考えます。

treefmt.toml
[formatter.md]
command = "markdownlint-cli2"
options = ["--config", "linter/.markdownlint-cli2.jsonc"]
includes = ["articles/*.md"]

treefmt を実行すると、以下のコマンドを実行したのと同じ結果が得られます。

Bash
markdownlint-cli2 --config linter/.markdownlint-cli2.jsonc articles/5b01a68b80808b.md
markdownlint-cli2 --config linter/.markdownlint-cli2.jsonc articles/0a5f92301e3b6f.md
# 以下略
# articles にある全ての .md ファイルを評価する
複数のリンターを使用する

先程の例だと markdownlint-cli のみでしたが、treefmt は複数のリンターを登録できます。

また、リンターを実行する順番は priority で制御可能です。

treefmt.toml
[formatter.md]
priority = 1

[formatter.text]
priority = 2

[formatter.YourLinterName]
priority = 3

この例だと、対象のファイルに対して md -> text -> YourLinterName の順でリンターが実行されます

除外設定

treefmt ではチェック対象外のファイルを指定できます。
除外設定は「リンターごと」「リンター全て」どちらも可能です。

treefmt.toml
[formatter.md]
excludes = ["articles/00341ed49b7935.md"]

[global]
excludes = ["articles/00341ed49b7935.md"]

今回紹介した設定の場合、リンター導入前に書いた記事で警告が鬱陶しいので、global で除外設定をしています。

5. 使用方法

以下を実行すると、リンターによるチェックが行われます。

Bash
treefmt --config-file ./linter/treefmt.toml

5.1 動作確認

長いので折り畳み

以下の文章を対象にリンターを実行しました。

警告がでる文章
文末に。がないし空白がある 

開かない URL。https://zenn.dev/trifolium/articles/007bff6324743
[存在しないセクションへジャンプ](#a)
[Zenn仕様の画像パス(存在しないファイル)](/images/5b01a68b80808b/hoge.webp)

javascript -> x, JavaScript -> o
JavaScrit タイポ。
Bash
 task
ERRO formatter | md[1]: failed to apply with options '[--config linter/.markdownlint-cli2.jsonc]': exit status 1

markdownlint-cli2 v0.18.1 (markdownlint v0.38.0)
Finding: articles/5b01a68b80808b.md
Linting: 1 file(s)
Summary: 2 error(s)
articles/5b01a68b80808b.md:857:14 MD009/no-trailing-spaces Trailing spaces [Expected: 0 or 2; Actual: 1]
articles/5b01a68b80808b.md:860:1 MD051/link-fragments Link fragments should be valid [Context: "[存在しないセクションへジャンプ](#a)"]

ERRO formatter | text[2]: failed to apply with options '[--config linter/.textlintrc.json]': exit status 1


/home/ryu/dev/zenn_contents/articles/5b01a68b80808b.md
  857:13  error    文末が"。"で終わっていません。                          ja-technical-writing/ja-no-mixed-period
  863:1   ✓ error  Incorrect term: “javascript”, use “JavaScript” instead  terminology

✖ 2 problems (2 errors, 0 warnings, 0 infos)1 fixable problem.
Try to run: $ textlint --fix [file]


ERRO formatter | url[3]: failed to apply with options '[--root-dir /home/ryu/dev/zenn_contents --timeout 10 --max-retries 2]': exit status 2

Issues found in 1 input. Find details below.

[articles/5b01a68b80808b.md]:
[ERROR] file:///home/ryu/dev/zenn_contents/images/5b01a68b80808b/hoge.webp | Cannot find file
[404] https://zenn.dev/trifolium/articles/007bff6324743 | Rejected status code (this depends on your "accept" configuration): Not Found

🔍 25 Total (in 0s)23 OK 🚫 2 Errors

ERRO formatter | spell[4]: failed to apply with options '[]': exit status 1

1/1 articles/5b01a68b80808b.md 635.59ms X
articles/5b01a68b80808b.md:864:5 - Unknown word (Scrit) fix: (Scrip, Script)
CSpell: Files checked: 1, Issues found: 1 in 1 file.

traversed 164 files
emitted 1 files for processing
formatted 0 files (0 changed) in 4.32s
Error: failed to finalise formatting: formatting failures detected
task: Failed to run task "default": exit status 1

6. VSCode 拡張の設定

6.1 拡張のインストール

markdownlint-cli2textlintcspell 用の VSCode 拡張が公開されています。

以下の extensions.json を参考にして、拡張をインストールします。

フォルダ構成
  zenn_contents/
  └─ .vscode/
+     └─ extensions.json
extensions.json
{
    "recommendations": [
        "streetsidesoftware.code-spell-checker",
        "davidanson.vscode-markdownlint",
        "3w36zj6.textlint"
    ]
}

6.2 設定

settings.json を作成します。

フォルダ構成
  zenn_contents/
  └─ .vscode/
      ├─ extensions.json
+     └─ settings.json
settings.json
{
    "textlint.configPath": "<your_path>/zenn_contents/linter/.textlintrc.json",
    "markdownlint.configFile": "<your_path>/zenn_contents/linter/.markdownlint-cli2.jsonc",
}

設定内容はシンプルで、コンフィグのパスを指定しているだけです。

cspell

Code Spell Checker はコンフィグのパスを指定する設定はありません

プロジェクト直下、もしくは、.vscode/ にある cspell.json を自動的に読み込む仕様です。

そのため、linter/cspell.json から .vscode/cspell.jsonシンボリックリンクを作成します。

以下をプロジェクト直下(zenn_contents)にて実行します。

Bash
ln -s ../linter/cspell.json .vscode/cspell.json
フォルダ構成
  zenn_contents/
  ├─ .vscode/
+ │  ├─ cspell.json  // シンボリックリンク
  │  ├─ extensions.json
  │  └─ settings.json
  └─ linter
      └─ cspell.json

6.3 動作確認

下画像のように問題タブにリアルタイムでリンターの警告が表示されます。

VSCode でのリンターの警告画面

おわりに

treefmt をハブにすることで、Markdown・日本語・リンク・スペルのチェックを一本化できました。

次回の記事では、Taskfile を利用してコマンド実行を楽にする方法を紹介しようと思います。

Discussion