npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@rewdy/md-formatter

v0.4.1

Published

A fast, opinionated Markdown formatter written in Rust

Readme

md-formatter

The mad formatter

The md-formatter ("mad formatter") is a fast, opinionated Markdown formatter written in Rust.

Why/Approach

Many now use modern tools for linting and formatting node code (biome, oxlint, etc), but these do not support formatting markdown. This tools is meant to provide a rust-based formatter for markdown only. The approach it takes is to parse the markdown with Rust's pulldown-cmark, then pump it back out with opinionated formatting. For simplicity, it explicitly ignores code blocks (for now), tables are handled minimally, and otherwise is pretty rudimentary.

Quick Start

Fast - Formats 360KB of Markdown in 4ms (~90MB/s)
Opinionated - Minimal configuration (--width, --wrap, --ordered-list)
Idempotent - format(format(x)) == format(x) guaranteed
Safe - Uses hard breaks to preserve structure across re-parsing
Complete - All CommonMark + GFM elements supported

Installation

Rust (CLI)

# Install from crates.io
cargo install md-formatter

# Or build from source
cargo build --release
./target/release/mdfmt --help

Node.js

# npm
npm install @rewdy/md-formatter

# pnpm
pnpm add @rewdy/md-formatter

# bun
bun add @rewdy/md-formatter

Usage

CLI (Rust)

# Format all markdown files in current directory (prints to stdout)
mdfmt .

# Format all markdown files in-place
mdfmt . --write

# Check if all files are formatted (for CI)
mdfmt . --check

# Format a specific file
mdfmt README.md

# Format multiple files or directories
mdfmt src/ docs/ README.md

# Custom line width
mdfmt . --width 100

# Read from stdin
cat file.md | mdfmt -

Glob Patterns

# Use glob patterns
mdfmt "**/*.md"

# Format files in a specific directory
mdfmt docs/

# Multiple paths
mdfmt src/ tests/ README.md

Exclusions

By default, mdfmt excludes common directories: node_modules, target, .git, vendor, dist, build.

# Add additional exclusions
mdfmt . --exclude my-vendor --exclude tmp

# Include everything (no default exclusions)
mdfmt . --no-default-excludes

Prose Wrapping

Control how prose (paragraph text) is wrapped with the --wrap option:

# Reflow prose to fit line width (default: 80)
mdfmt . --wrap always

# Unwrap prose into single lines per paragraph
mdfmt . --wrap never

# Keep existing line breaks (default)
mdfmt . --wrap preserve

| Mode | Description | |------|-------------| | always | Reflow text to fit within line width | | never | Unwrap each paragraph to a single long line | | preserve | Leave existing line breaks unchanged (default) |

Ordered Lists

Control how ordered list items are numbered with the --ordered-list option:

# Renumber items sequentially (default)
mdfmt . --ordered-list ascending

# Use 1. for all items
mdfmt . --ordered-list one

| Mode | Description | |------|-------------| | ascending | Renumber items sequentially: 1, 2, 3, ... (default) | | one | Use 1. for all items |

Integration

# Pre-commit hook
mdfmt . --check

# CI pipeline
mdfmt . --check || exit 1

# Format only changed files
git diff --name-only -- '*.md' | xargs mdfmt --write

Node.js Integration

The npm package includes the mdfmt binary, making it easy to add markdown formatting to your existing Node.js toolchain alongside Biome, ESLint, or other tools.

package.json Scripts

{
  "scripts": {
    "format": "biome format --write . && mdfmt . --write",
    "format:check": "biome format . && mdfmt . --check",
    "format:md": "mdfmt . --write",
    "format:md:check": "mdfmt . --check",
    "lint": "biome lint .",
    "check": "biome check . && mdfmt . --check"
  }
}

CI Example (GitHub Actions)

- name: Check formatting
  run: |
    pnpm biome format .
    pnpm mdfmt . --check

With Husky/lint-staged

{
  "lint-staged": {
    "*.{js,ts,json}": ["biome check --write"],
    "*.md": ["mdfmt --write"]
  }
}

Programmatic API

For advanced use cases, you can also use the formatter programmatically:

import { formatMarkdown, checkMarkdown } from '@rewdy/md-formatter';

// Format a string
const formatted = formatMarkdown(input, {
  width: 80,
  wrap: 'preserve',
  orderedList: 'ascending'
});

// Check if formatted (returns boolean)
const isFormatted = checkMarkdown(input);

Formatting Rules

Supported Elements

  • Paragraphs (line breaks controlled by --wrap mode)
  • Headings (normalized to # Heading format)
  • Lists (unordered -, ordered with --ordered-list mode, with nesting)
  • Blockquotes (with > prefix per depth)
  • Code blocks (fenced, language tags preserved)
  • Inline code, emphasis, links
  • Horizontal rules (normalized to ---)
  • Frontmatter (YAML blocks preserved)
  • GFM strikethrough and autolinks

Design Philosophy

Uses hard breaks (two spaces + newline) instead of soft breaks to ensure
idempotence. This prevents Markdown parsers from reinterpreting wrapped lines as
soft breaks on re-parsing.

Performance

| Scenario | Time | Throughput | | ----------------- | ------- | ---------- | | 360KB file | 4ms | ~90MB/s | | Average file (2KB)| <1ms | Instant |

Architecture

Input Markdown
    ↓
Extract Frontmatter (if present)
    ↓
Parse to Event Stream (pulldown-cmark)
    ↓
Format Events (state machine with hard breaks)
    ↓
Prepend Frontmatter
    ↓
Output Markdown

The formatter never parses the output, so idempotence is guaranteed by design.

CLI Options

Usage: mdfmt [OPTIONS] [PATH]...

Arguments:
  [PATH]...  Files or directories to format (supports glob patterns, use - for stdin)

Options:
  -w, --write                   Write formatted output to file in-place
      --check                   Check if files are formatted (exit with 1 if not)
      --stdin                   Read from stdin
      --width <WIDTH>           Line width for wrapping [default: 80]
      --wrap <MODE>             How to wrap prose: always, never, preserve [default: preserve]
      --ordered-list <MODE>     How to number ordered lists: ascending, one [default: ascending]
      --exclude <DIR>           Additional directories to exclude
      --no-default-excludes     Don't exclude any directories by default
  -h, --help                    Print help
  -V, --version                 Print version

Default exclusions: node_modules, target, .git, vendor, dist, build

Testing

# Run all tests
cargo test --release --lib

# Run specific test
cargo test --release --lib test_idempotence -- --nocapture

# Build release binary
cargo build --release

Current status: 25 unit tests passing ✓

Known Limitations

  • Autolinks - Converted to regular links (parser limitation)
  • Configuration - Only --width, --wrap, and --ordered-list options supported (by design)
  • MDX - Not supported (different language)

Project Structure

src/
├── main.rs       - CLI entry point and file I/O
├── cli.rs        - Argument parsing (clap)
├── formatter.rs  - Core formatting logic (~430 lines)
├── parser.rs     - Markdown parsing and frontmatter extraction
└── lib.rs        - Public API and unit tests (14 tests)

Status

Version: 0.1.1
MVP: Complete ✓
Tests: 25/25 passing ✓
Idempotence: Verified ✓
Performance: Excellent ✓

See STATUS.md for detailed feature matrix and quality metrics.

Contributing

As long as changes are conceptually in-line with the project, I welcome all contributions. 😄

License

MIT