Skip to content
markdownlint-integration logo

Markdownlint Integration

markdownlint-integration

Integrate markdownlint into development workflows including CLI usage, programmatic API, CI/CD pipelines, and editor integration.

TheBushidoCollective/han0installs199stars

SKILL.md

Full skill instructions

Markdownlint Integration

Master integrating markdownlint into development workflows including CLI usage, programmatic API (sync/​async/​promise), CI/​CD pipelines, pre-commit hooks, and editor integration.

Overview

Markdownlint can be integrated into various parts of your development workflow to ensure consistent markdown quality. This includes command-line tools, programmatic usage in Node.js, continuous integration pipelines, Git hooks, and editor plugins.

Command-Line Interface

markdownlint-cli Installation

npm install -g markdownlint-cli
# or as dev dependency
npm install --save-dev markdownlint-cli

Basic CLI Usage

# Lint all markdown files in current directory
markdownlint '**/​*.md'

# Lint specific files
markdownlint README.md CONTRIBUTING.md

# Lint with configuration file
markdownlint -c .markdownlint.json '**/​*.md'

# Ignore specific files
markdownlint '**/​*.md' --ignore node_modules

# Fix violations automatically
markdownlint -f '**/​*.md'

# Output to file
markdownlint '**/​*.md' -o linting-results.txt

Advanced CLI Options

# Use custom config
markdownlint --config config/​markdown-lint.json docs/

# Ignore patterns from file
markdownlint --ignore-path .gitignore '**/​*.md'

# Use multiple ignore patterns
markdownlint --ignore node_modules --ignore dist '**/​*.md'

# Enable specific rules only
markdownlint --rules MD001,MD003,MD013 '**/​*.md'

# Disable specific rules
markdownlint --disable MD013 '**/​*.md'

# Show output in JSON format
markdownlint --json '**/​*.md'

# Quiet mode (exit code only)
markdownlint -q '**/​*.md'

# Verbose output
markdownlint --verbose '**/​*.md'

CLI Configuration File

.markdownlint-cli.json:

{
  "config": {
    "default": true,
    "MD013": {
      "line_length": 100
    }
  },
  "files": ["**/​*.md"],
  "ignores": [
    "node_modules/​**",
    "dist/​**",
    "build/​**"
  ]
}

Use with:

markdownlint --config .markdownlint-cli.json

Programmatic API

Callback-Based API

const markdownlint = require('markdownlint');

const options = {
  files: ['good.md', 'bad.md'],
  config: {
    default: true,
    'line-length': {
      line_length: 100
    }
  }
};

markdownlint(options, (err, result) => {
  if (!err) {
    console.log(result.toString());
  } else {
    console.error(err);
  }
});

Promise-Based API

import { lint as lintPromise } from 'markdownlint/​promise';

const options = {
  files: ['README.md', 'docs/​**/​*.md'],
  config: {
    default: true,
    'no-inline-html': {
      allowed_elements: ['br', 'img']
    }
  }
};

try {
  const results = await lintPromise(options);
  console.dir(results, { colors: true, depth: null });
} catch (err) {
  console.error(err);
}

Synchronous API

import { lint as lintSync } from 'markdownlint/​sync';

const options = {
  files: ['README.md'],
  strings: {
    'inline-content': '# Test\n\nContent here.'
  },
  config: {
    default: true
  }
};

const results = lintSync(options);
console.log(results.toString());

Linting Strings

const markdownlint = require('markdownlint');

const options = {
  strings: {
    'content-1': '# Heading\n\nParagraph text.',
    'content-2': '## Another heading\n\nMore content.'
  },
  config: {
    default: true,
    'first-line-heading': {
      level: 1
    }
  }
};

markdownlint(options, (err, result) => {
  if (!err) {
    const resultString = result.toString();
    console.log(resultString);
  }
});

Working with Results

import { lint } from 'markdownlint/​promise';

const results = await lint({
  files: ['docs/​**/​*.md'],
  config: { default: true }
});

// Results is an object keyed by filename
Object.keys(results).forEach(file => {
  const fileResults = results[file];

  fileResults.forEach(result => {
    console.log(`${file}:${result.lineNumber} ${result.ruleNames.join('/')} ${result.ruleDescription}`);

    if (result.errorDetail) {
      console.log(`  Detail: ${result.errorDetail}`);
    }

    if (result.errorContext) {
      console.log(`  Context: ${result.errorContext}`);
    }
  });
});

Reading Configuration

const markdownlint = require('markdownlint');
const { readConfigSync } = require('markdownlint/​sync');

// Read configuration from file
const config = readConfigSync('.markdownlint.json');

const options = {
  files: ['**/​*.md'],
  config: config
};

const results = markdownlint.sync(options);
console.log(results.toString());

Using Custom Rules

const markdownlint = require('markdownlint');
const customRule = require('./​custom-rules/​heading-capitalization');

const options = {
  files: ['README.md'],
  config: {
    default: true,
    'heading-capitalization': true
  },
  customRules: [customRule]
};

markdownlint(options, (err, result) => {
  if (!err) {
    console.log(result.toString());
  }
});

Applying Fixes Programmatically

applyFix Function

const { applyFix } = require('markdownlint');

const line = '  Text with extra spaces  ';
const fixInfo = {
  editColumn: 1,
  deleteCount: 2,
  insertText: ''
};

const fixed = applyFix(line, fixInfo);
console.log(fixed); // 'Text with extra spaces  '

applyFixes Function

const { applyFixes } = require('markdownlint');

const input = '# Heading\n\n\nParagraph';
const errors = [
  {
    lineNumber: 3,
    ruleNames: ['MD012'],
    ruleDescription: 'Multiple blank lines',
    fixInfo: {
      lineNumber: 3,
      deleteCount: -1
    }
  }
];

const fixed = applyFixes(input, errors);
console.log(fixed); // '# Heading\n\nParagraph'

Auto-Fix Workflow

const fs = require('fs');
const markdownlint = require('markdownlint');
const { applyFixes } = require('markdownlint');

const file = 'README.md';
const content = fs.readFileSync(file, 'utf8');

const options = {
  strings: {
    [file]: content
  },
  config: {
    default: true
  }
};

markdownlint(options, (err, result) => {
  if (!err) {
    const errors = result[file] || [];

    if (errors.length > 0) {
      const fixed = applyFixes(content, errors);
      fs.writeFileSync(file, fixed, 'utf8');
      console.log(`Fixed ${errors.length} issues in ${file}`);
    }
  }
});

CI/​CD Integration

GitHub Actions

.github/​workflows/​markdownlint.yml:

name: Markdownlint
user-invocable: false

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  lint:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/​checkout@v3

      - name: Setup Node.js
        uses: actions/​setup-node@v3
        with:
          node-version: '18'

      - name: Install dependencies
        run: npm ci

      - name: Run markdownlint
        run: npx markdownlint '**/​*.md' --ignore node_modules

      - name: Annotate PR with results
        if: failure()
        run: |
          npx markdownlint '**/​*.md' --ignore node_modules -o markdownlint-results.txt
          cat markdownlint-results.txt

GitHub Actions with markdownlint-cli2

name: Markdownlint
user-invocable: false

on: [push, pull_request]

jobs:
  lint:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/​checkout@v3

      - name: Run markdownlint-cli2
        uses: DavidAnson/​markdownlint-cli2-action@v9
        with:
          paths: '**/​*.md'

GitLab CI

.gitlab-ci.yml:

markdownlint:
  image: node:18-alpine
  stage: test
  before_script:
    - npm install -g markdownlint-cli
  script:
    - markdownlint '**/​*.md' --ignore node_modules
  only:
    - merge_requests
    - main
  artifacts:
    when: on_failure
    paths:
      - markdownlint-results.txt

CircleCI

.circleci/​config.yml:

version: 2.1

jobs:
  markdownlint:
    docker:
      - image: cimg/​node:18.0
    steps:
      - checkout
      - run:
          name: Install markdownlint
          command: npm install -g markdownlint-cli
      - run:
          name: Run linter
          command: markdownlint '**/​*.md' --ignore node_modules

workflows:
  version: 2
  build_and_test:
    jobs:
      - markdownlint

Jenkins Pipeline

Jenkinsfile:

pipeline {
    agent any

    stages {
        stage('Lint Markdown') {
            steps {
                sh 'npm install -g markdownlint-cli'
                sh 'markdownlint "**/​*.md" --ignore node_modules'
            }
        }
    }

    post {
        always {
            cleanWs()
        }
        failure {
            echo 'Markdownlint found issues!'
        }
    }
}

Azure Pipelines

azure-pipelines.yml:

trigger:
  - main

pool:
  vmImage: 'ubuntu-latest'

steps:
  - task: NodeTool@0
    inputs:
      versionSpec: '18.x'
    displayName: 'Install Node.js'

  - script: |
      npm install -g markdownlint-cli
    displayName: 'Install markdownlint'

  - script: |
      markdownlint '**/​*.md' --ignore node_modules
    displayName: 'Run markdownlint'

Pre-commit Hooks

Using Husky

Install Husky:

npm install --save-dev husky
npx husky install

Create pre-commit hook:

npx husky add .husky/​pre-commit "npm run lint:md"

Add script to package.json:

{
  "scripts": {
    "lint:md": "markdownlint '**/​*.md' --ignore node_modules",
    "lint:md:fix": "markdownlint -f '**/​*.md' --ignore node_modules"
  }
}

Using lint-staged

Install lint-staged:

npm install --save-dev lint-staged

Configure in package.json:

{
  "lint-staged": {
    "*.md": [
      "markdownlint --fix",
      "git add"
    ]
  }
}

Update pre-commit hook:

npx husky add .husky/​pre-commit "npx lint-staged"

Using pre-commit Framework

.pre-commit-config.yaml:

repos:
  - repo: https://github.com/​igorshubovych/​markdownlint-cli
    rev: v0.37.0
    hooks:
      - id: markdownlint
        args: ['--fix']

  - repo: https://github.com/​DavidAnson/​markdownlint-cli2
    rev: v0.10.0
    hooks:
      - id: markdownlint-cli2
        args: ['--fix']

Install and use:

pip install pre-commit
pre-commit install
pre-commit run --all-files

Editor Integration

Visual Studio Code

Install the markdownlint extension:

  1. Open VS Code
  2. Go to Extensions (Cmd+Shift+X)
  3. Search for "markdownlint"
  4. Install "markdownlint" by David Anson

Configure in .vscode/​settings.json:

{
  "markdownlint.config": {
    "default": true,
    "MD013": {
      "line_length": 100
    }
  },
  "markdownlint.ignore": [
    "node_modules/​**",
    "dist/​**"
  ],
  "editor.codeActionsOnSave": {
    "source.fixAll.markdownlint": true
  }
}

Vim/​Neovim

Using ALE (Asynchronous Lint Engine):

" In .vimrc or init.vim
let g:ale_linters = {
\   'markdown': ['markdownlint'],
\}

let g:ale_fixers = {
\   'markdown': ['markdownlint'],
\}

let g:ale_markdown_markdownlint_options = '-c .markdownlint.json'

" Enable fixing on save
let g:ale_fix_on_save = 1

Sublime Text

Install via Package Control:

  1. Install Package Control
  2. Install "SublimeLinter"
  3. Install "SublimeLinter-contrib-markdownlint"

Configure in preferences:

{
  "linters": {
    "markdownlint": {
      "args": ["-c", ".markdownlint.json"]
    }
  }
}

Atom

Install packages:

apm install linter-markdownlint

Configure in Atom settings or .atom/​config.cson:

"linter-markdownlint":
  configPath: ".markdownlint.json"

npm Scripts Integration

package.json Scripts

{
  "scripts": {
    "lint": "npm run lint:md",
    "lint:md": "markdownlint '**/​*.md' --ignore node_modules",
    "lint:md:fix": "markdownlint -f '**/​*.md' --ignore node_modules",
    "lint:md:ci": "markdownlint '**/​*.md' --ignore node_modules -o markdownlint-report.txt",
    "test": "npm run lint && npm run test:unit",
    "precommit": "lint-staged"
  }
}

Cross-Platform Compatibility

Using cross-env for environment variables:

{
  "scripts": {
    "lint:md": "cross-env NODE_ENV=development markdownlint '**/​*.md'"
  },
  "devDependencies": {
    "cross-env": "^7.0.3"
  }
}

Docker Integration

Dockerfile

FROM node:18-alpine

WORKDIR /​app

# Install markdownlint globally
RUN npm install -g markdownlint-cli

# Copy markdown files
COPY . .

# Run linter
CMD ["markdownlint", "**/​*.md", "--ignore", "node_modules"]

docker-compose.yml

version: '3.8'

services:
  markdownlint:
    image: node:18-alpine
    working_dir: /​app
    volumes:
      - .:/​app
    command: >
      sh -c "npm install -g markdownlint-cli &&
             markdownlint '**/​*.md' --ignore node_modules"

Run with:

docker-compose run markdownlint

Monorepo Integration

Workspace Configuration

Root .markdownlint.json:

{
  "default": true,
  "line-length": {
    "line_length": 100
  }
}

Root package.json:

{
  "scripts": {
    "lint:md": "markdownlint '**/​*.md' --ignore node_modules",
    "lint:md:packages": "lerna run lint:md",
    "lint:md:all": "npm run lint:md && npm run lint:md:packages"
  }
}

Package-level packages/​api/​package.json:

{
  "scripts": {
    "lint:md": "markdownlint '**/​*.md'"
  }
}

Using Lerna

{
  "scripts": {
    "lint:md": "lerna run lint:md --stream"
  }
}

Using Turborepo

turbo.json:

{
  "pipeline": {
    "lint:md": {
      "outputs": []
    }
  }
}

Reporting and Metrics

Generate HTML Report

Using a custom script:

const fs = require('fs');
const markdownlint = require('markdownlint');

const options = {
  files: ['**/​*.md'],
  config: { default: true }
};

markdownlint(options, (err, results) => {
  if (!err) {
    const html = generateHtmlReport(results);
    fs.writeFileSync('markdownlint-report.html', html);
  }
});

function generateHtmlReport(results) {
  let html = '<html><head><title>Markdownlint Report</​title></​head><body>';
  html += '<h1>Markdownlint Results</​h1>';

  Object.keys(results).forEach(file => {
    html += `<h2>${file}</​h2>`;
    html += '<ul>';

    results[file].forEach(result => {
      html += `<li>Line ${result.lineNumber}: ${result.ruleDescription}</​li>`;
    });

    html += '</​ul>';
  });

  html += '</​body></​html>';
  return html;
}

JSON Output for Tooling

markdownlint '**/​*.md' --json > results.json

Process with jq:

markdownlint '**/​*.md' --json | jq '.[] | select(length > 0)'

When to Use This Skill

  • Setting up markdownlint in new projects
  • Integrating linting into CI/​CD pipelines
  • Configuring pre-commit hooks
  • Automating documentation quality checks
  • Setting up editor integration
  • Building custom linting workflows
  • Creating automated fix scripts
  • Implementing documentation standards

Best Practices

  1. CI/​CD Integration - Always run linting in continuous integration
  2. Pre-commit Hooks - Catch issues before they reach version control
  3. Editor Integration - Get real-time feedback while writing
  4. Consistent Configuration - Use same config across all environments
  5. Auto-fix When Possible - Use -f flag to automatically fix violations
  6. Fail Fast - Configure CI to fail on linting errors
  7. Ignore Generated Files - Exclude build artifacts and dependencies
  8. Version Lock - Pin markdownlint version in package.json
  9. Document Standards - Keep documentation on linting rules
  10. Progressive Enhancement - Start with relaxed rules, tighten over time
  11. Team Communication - Discuss rule changes before applying
  12. Regular Updates - Keep markdownlint updated for bug fixes
  13. Performance Optimization - Use appropriate glob patterns
  14. Error Reporting - Configure meaningful error output
  15. Backup Before Auto-fix - Always commit before running fixes

Common Pitfalls

  1. Missing Ignore Patterns - Linting node_modules or build directories
  2. Wrong Glob Patterns - Incorrect file matching in CLI
  3. Config Not Found - Configuration file in wrong location
  4. Async/​Sync Mismatch - Using sync API with async rules
  5. CI Timeout - Linting too many files without optimization
  6. No Exit Code Check - Not failing CI on linting errors
  7. Overwriting Files - Using auto-fix without version control
  8. Missing Dependencies - Not installing markdownlint in CI
  9. Platform Differences - Path separators differ between OS
  10. Large Binary Files - Accidentally linting non-markdown files
  11. Outdated Cache - Cached node_modules with old markdownlint
  12. Silent Failures - Not capturing error output in CI
  13. Config Conflicts - Multiple config files conflicting
  14. Missing Editor Config - Local linting differs from CI
  15. No Pre-commit Hook - Issues not caught before commit

Resources

More skills from TheBushidoCollective

typescript-utility-types logo
TheBushidoCollective/han

typescript-utility-types

Use when typeScript utility types, mapped types, and advanced type manipulation. Use when creating flexible, type-safe TypeScript code.

199 0
View
shell-portability logo
TheBushidoCollective/han

shell-portability

Use when writing shell scripts that need to run across different systems, shells, or environments. Covers POSIX compatibility and platform differences.

199 0
View
tailwind-performance logo
TheBushidoCollective/han

tailwind-performance

Use when optimizing Tailwind CSS for production, reducing bundle size, and improving performance. Covers PurgeCSS, JIT mode, and build optimization.

199 0
View
figma-generate-component logo
TheBushidoCollective/han

figma-generate-component

Generate production-ready code from a Figma component or frame using the Figma MCP server

199 0
View
dialyzer-integration logo
TheBushidoCollective/han

dialyzer-integration

Use when integrating Dialyzer into development workflows and CI/CD pipelines for Erlang/Elixir projects.

199 0
View
rubocop-configuration logo
TheBushidoCollective/han

rubocop-configuration

Use when ruboCop configuration including YAML config, departments, inheritance, and customization.

199 0
View
elixir-otp-patterns logo
TheBushidoCollective/han

elixir-otp-patterns

Use when Elixir OTP patterns including GenServer, Supervisor, Agent, and Task. Use when building concurrent, fault-tolerant Elixir applications.

199 0
View
ameba-custom-rules logo
TheBushidoCollective/han

ameba-custom-rules

Use when creating custom Ameba rules for Crystal code analysis including rule development, AST traversal, issue reporting, and rule testing.

199 0
View
Swift Optionals Patterns logo
TheBushidoCollective/han

Swift Optionals Patterns

Use when swift's optional handling patterns including optional binding, chaining, nil coalescing, and modern approaches to safely working with optional values while avoiding common pitfalls and force unwrapping.

199 0
View
code-annotation-patterns logo
TheBushidoCollective/han

code-annotation-patterns

Use when annotating code with structured metadata, tags, and markers for AI-assisted development workflows. Covers annotation formats, semantic tags, and integration with development tools.

199 0
View
Scala Collections logo
TheBushidoCollective/han

Scala Collections

Use when scala collections including immutable/mutable variants, List, Vector, Set, Map operations, collection transformations, lazy evaluation with views, parallel collections, and custom collection builders for efficient data processing.

199 0
View
reactflow-custom-nodes logo
TheBushidoCollective/han

reactflow-custom-nodes

Use when creating custom React Flow nodes, edges, and handles. Covers custom node components, resizable nodes, toolbars, and advanced customization.

199 0
View

Popular AI tools

Kaiber logo
Video

Kaiber

Generate, edit, and beat-sync AI video with leading models in one workspace.

Paid
View
Vimcal logo
Productivity

Vimcal

The world's fastest calendar for remote work

Free
View

Transform Your Design with AI Designer by ImgCreator.ai

Freemium
View
Akool AI logo
Content & writing

Akool AI

Revolutionizing Video Production with AI-Powered Creativity

Paid
View

Extend an image past the frame and let AI fill the new aspect ratio.

Freemium
View
StarByFace logo
Security

StarByFace

Discover your celebrity doppelgänger with StarByFace!

Free
View
C

ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.

Freemium
View
Opus Clip logo
Coding & apps

Opus Clip

Opus.ai: Revolutionize Your Web Experience

Free
View