🔧 markdownlint-cli2#

A fast CLI tool to lint Markdown files against a set of rules. Catches bad formatting, missing code block languages, trailing spaces, and inconsistent structure — before they reach GitHub.


📦 Install#

npm install --save-dev markdownlint-cli2

Requires Node.js. No global install needed — use via npx or npm run scripts.


✅ Core Use Cases#

  • Enforce consistent Markdown formatting across all .md files
  • Block bad commits via a git pre-commit hook (via Husky)
  • Fail CI/CD pipelines on unformatted Markdown
  • Auto-fix many common issues with --fix

🔑 Key Commands / Usage#

Check all Markdown files#

npm run lint:md
# or directly:
npx markdownlint-cli2 "**/*.md" "#node_modules" "#themes" "#public"

What it does: Scans all .md files, prints violations with file + line number.


Auto-fix fixable issues#

npm run lint:md:fix
# or directly:
npx markdownlint-cli2 --fix "**/*.md" "#node_modules" "#themes" "#public"

What it does: Automatically fixes trailing spaces, blank lines around fences, multiple blank lines. Does NOT fix missing code block languages — those need manual attention.


What --fix can and cannot fix#

RuleAuto-fixed?
MD009 trailing spaces
MD012 multiple blank lines
MD031 blank lines around fences
MD040 missing code block language❌ Must be added manually

⚙️ Config / Setup#

package.json scripts#

{
  "scripts": {
    "lint:md": "markdownlint-cli2 \"**/*.md\" \"#node_modules\" \"#themes\" \"#public\"",
    "lint:md:fix": "markdownlint-cli2 --fix \"**/*.md\" \"#node_modules\" \"#themes\" \"#public\""
  },
  "devDependencies": {
    "markdownlint-cli2": "^0.17.0"
  }
}

#themes and #public exclude the Hugo theme submodule and build output from linting.


.markdownlint.json — rules config (relaxed for notes)#

{
  "MD013": false,
  "MD033": false,
  "MD034": false,
  "MD041": false,
  "MD007": { "indent": 2 },
  "MD009": true,
  "MD012": { "maximum": 2 },
  "MD022": true,
  "MD025": true,
  "MD031": true,
  "MD032": true
}
RuleWhat it doesEnabled?
MD013Max line length (80 chars)❌ Off — too strict for notes
MD033No inline HTML❌ Off — useful in notes
MD034No bare URLs❌ Off — notes have plain links
MD041First line must be H1❌ Off — frontmatter comes first
MD009No trailing spaces
MD012Max 2 consecutive blank lines
MD022Blank lines around headings
MD025Only one H1 per file
MD031Blank lines around fences
MD032Blank lines around lists

Pre-commit hook (via Husky) — .husky/pre-commit#

The hook is managed by Husky and auto-installs via npm install. See husky.

#!/bin/sh

STAGED_MD=$(git diff --cached --name-only --diff-filter=ACM | grep '\.md$' || true)

if [ -z "$STAGED_MD" ]; then
  echo "⏭️  No .md files staged, skipping markdownlint."
  exit 0
fi

echo "🔍 Running markdownlint on staged files..."
echo "$STAGED_MD" | xargs npx markdownlint-cli2

if [ $? -ne 0 ]; then
  echo ""
  echo "❌ Markdownlint failed. Fix the issues above before committing."
  echo "   To skip (not recommended): git commit --no-verify"
  exit 1
fi

echo "✅ Markdownlint passed."
exit 0

|| true after grep prevents exit code 1 from propagating when no .md files are staged.


GitHub Actions CI — .github/workflows/lint.yml#

name: Lint Markdown

on: [push, pull_request]

jobs:
  markdownlint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - run: npm install
      - run: npm run lint:md

Blocks any PR that has Markdown violations — cannot be bypassed unlike the local hook.


⚠️ Gotchas & Notes#

  • package-lock.json must be committed — required for CI npm install to work.
  • --fix is safe to run anytime — it only touches fixable issues and doesn’t change content/meaning.
  • To bypass the hook in an emergency: git commit --no-verify (use sparingly).
  • #themes and #public must be excluded or the Hugo theme’s own markdown files get linted.

  • husky — manages the pre-commit hook
  • eslint — same concept but for JavaScript/TypeScript