name: markdownlint-integration description: Integrate markdownlint into development workflows including CLI usage, programmatic API, CI/CD pipelines, and editor integration. allowed-tools: [Bash, Read]
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
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
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:
- Open VS Code
- Go to Extensions (Cmd+Shift+X)
- Search for "markdownlint"
- 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:
- Install Package Control
- Install "SublimeLinter"
- 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
- CI/CD Integration - Always run linting in continuous integration
- Pre-commit Hooks - Catch issues before they reach version control
- Editor Integration - Get real-time feedback while writing
- Consistent Configuration - Use same config across all environments
- Auto-fix When Possible - Use -f flag to automatically fix violations
- Fail Fast - Configure CI to fail on linting errors
- Ignore Generated Files - Exclude build artifacts and dependencies
- Version Lock - Pin markdownlint version in package.json
- Document Standards - Keep documentation on linting rules
- Progressive Enhancement - Start with relaxed rules, tighten over time
- Team Communication - Discuss rule changes before applying
- Regular Updates - Keep markdownlint updated for bug fixes
- Performance Optimization - Use appropriate glob patterns
- Error Reporting - Configure meaningful error output
- Backup Before Auto-fix - Always commit before running fixes
Common Pitfalls
- Missing Ignore Patterns - Linting node_modules or build directories
- Wrong Glob Patterns - Incorrect file matching in CLI
- Config Not Found - Configuration file in wrong location
- Async/Sync Mismatch - Using sync API with async rules
- CI Timeout - Linting too many files without optimization
- No Exit Code Check - Not failing CI on linting errors
- Overwriting Files - Using auto-fix without version control
- Missing Dependencies - Not installing markdownlint in CI
- Platform Differences - Path separators differ between OS
- Large Binary Files - Accidentally linting non-markdown files
- Outdated Cache - Cached node_modules with old markdownlint
- Silent Failures - Not capturing error output in CI
- Config Conflicts - Multiple config files conflicting
- Missing Editor Config - Local linting differs from CI
- No Pre-commit Hook - Issues not caught before commit
Resources
Expert Next.js App Router
Developpement
Un skill qui transforme Claude en expert Next.js App Router.
Générateur de README
Developpement
Crée des README.md professionnels et complets pour vos projets.
Rédacteur de Documentation API
Developpement
Génère de la documentation API complète au format OpenAPI/Swagger.