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 🙏

© 2026 – Pkg Stats / Ryan Hefner

md-pen

v1.2.0

Published

Utilities for formatting Markdown

Readme

Typed utilities for formatting Markdown. GFM (GitHub-Flavored Markdown) first.

Features

  • Zero dependencies
  • Full TypeScript with exported types
  • Every output verified against a CommonMark parser
  • GitHub-compatible (audited against cmark-gfm)
  • Functions compose naturally

Install

npm install md-pen

Usage

import {
    bold, code, link, table
} from 'md-pen'

bold('important') // __important__
code('git status') // `git status`
link('https://github.com', 'GitHub') // [GitHub](https://github.com)

table([
    ['Name', 'Age'],
    ['Alice', '30']
])
// | Name | Age |
// | - | - |
// | Alice | 30 |

A default export is also available: import md from 'md-pen' then md.bold(), md.table(), etc.

API

Inline

code(text)

Wraps in backtick code span. Handles backticks in content automatically.

code('hello') // `hello`
code('a `b` c') // `` a `b` c ``

bold(text)

bold('important') // __important__

italic(text)

italic('emphasis') // *emphasis*

strikethrough(text)

strikethrough('no') // ~~no~~

link(url, text?, options?)

Markdown by default. Falls back to HTML when options go beyond what markdown supports.

link('https://x.com') // <https://x.com>
link('/docs/guide') // [/docs/guide](/docs/guide)
link('https://x.com', 'click') // [click](https://x.com)
link('https://x.com', 'click', { title: 'T' }) // [click](https://x.com "T")

// HTML fallback for attributes markdown can't express
link('https://x.com', 'click', { target: '_blank' })
// <a target="_blank" href="https://x.com">click</a>

image(url, alt?, options?)

Same fallback principle as link.

image('cat.png') // ![](cat.png)
image('cat.png', 'A cat') // ![A cat](cat.png)
image('cat.png', 'A cat', { title: 'T' }) // ![A cat](cat.png "T")

// HTML fallback for width/height
image('cat.png', 'A cat', {
    width: 200,
    height: 100
})
// <img width="200" height="100" src="cat.png" alt="A cat" />

Block

heading(text, level?) / h1-h6

heading('Title') // # Title
heading('Sub', 2) // ## Sub
h1('Title') // # Title
h3('Section') // ### Section

blockquote(text)

Prefixes each line with > .

blockquote('line 1\nline 2')
// > line 1
// > line 2

codeBlock(code, language?)

Fenced code block. Handles content containing backtick fences.

codeBlock('const x = 1', 'ts')
// ```ts
// const x = 1
// ```

table(rows, options?)

First row is the header. Cells auto-stringify numbers and booleans. Ragged rows are padded.

table([
    ['Name', 'Age'],
    ['Alice', 30]
], { align: ['left', 'right'] })
// | Name | Age |
// | :- | -: |
// | Alice | 30 |

Also accepts an array of objects (keys become headers):

table([
    {
        name: 'Alice',
        age: 30
    },
    {
        name: 'Bob',
        age: 25
    }
])
// | name | age |
// | - | - |
// | Alice | 30 |
// | Bob | 25 |

Use columns to control order, filter, and rename. Each entry is a key or [key, header] tuple:

table([
    {
        firstName: 'Alice',
        age: 30,
        id: 1
    }
], {
    columns: [['firstName', 'Name'], 'age']
})
// | Name | age |
// | - | - |
// | Alice | 30 |

Use html: true for block content in cells (code blocks, lists, etc.):

table([
    ['Before', 'After'],
    [codeBlock('old()', 'js'), codeBlock('updated()', 'js')]
], { html: true })
// Outputs an HTML <table> with markdown-rendered cells

Alignment: 'left', 'center', 'right', 'none'

[!NOTE] In markdown mode, newlines become <br> and boundary whitespace becomes &nbsp;, so those literal strings can't be represented in cells. Use html: true for exact content preservation.

ul(items) / ol(items)

Nested arrays become children of the preceding item.

ul(['a', 'b', ['nested 1', 'nested 2'], 'c'])
// - a
// - b
//   - nested 1
//   - nested 2
// - c

ol(['first', 'second', ['sub-a'], 'third'])
// 1. first
// 2. second
//    1. sub-a
// 3. third

hr()

hr() // ---

GFM Extras

taskList(items)

taskList([
    [true, 'Done'],
    [false, 'Todo']
])
// - [x] Done
// - [ ] Todo

alert(type, content)

Types: 'note', 'tip', 'important', 'warning', 'caution'

alert('warning', 'Be careful') // eslint-disable-line no-alert
// > [!WARNING]
// > Be careful

footnoteRef(id) / footnote(id, text)

footnoteRef('1') // [^1]
footnote('1', 'Source') // [^1]: Source

details(summary, content, options?)

Collapsible section. Summary is HTML-escaped, content supports markdown.

details('Click to expand', 'Hidden **markdown** here')
// <details>
// <summary>Click to expand</summary>
//
// Hidden **markdown** here
//
// </details>

details('Expanded', 'Visible', { open: '' })
// <details open="">
// <summary>Expanded</summary>
//
// Visible
//
// </details>

Niche

kbd(key, options?)

kbd('Ctrl') // <kbd>Ctrl</kbd>
kbd('Enter', { title: 'Confirm' }) // <kbd title="Confirm">Enter</kbd>

sub(text, options?) / sup(text, options?)

sub('2') // <sub>2</sub>
sup('n') // <sup>n</sup>
sub('2', { title: 'subscript' }) // <sub title="subscript">2</sub>

math(expression) / mathBlock(expression)

math('E = mc^2') // $E = mc^2$

mathBlock(String.raw`\sum_{i=1}^n x_i`)
// $$
// \sum_{i=1}^n x_i
// $$

[!NOTE] Inline math() cannot render expressions ending with \ (the backslash escapes the closing $). Use mathBlock() instead.

mermaid(code)

Sugar for codeBlock(code, 'mermaid').

mermaid('graph TD;\n  A-->B;')
// ```mermaid
// graph TD;
//   A-->B;
// ```

mention(username) / emoji(name)

mention('octocat') // @octocat
emoji('rocket') // :rocket:

Generic

el(tag, attributes?, content?)

Generic HTML element builder for tags without a dedicated function. Attribute values are HTML-escaped and attribute names are sanitized against injection. Content is raw (not escaped). Without content, produces a self-closing tag.

el('br') // <br />
el('img', {
    src: 'cat.png',
    alt: 'A cat'
}) // <img src="cat.png" alt="A cat" />
el('p', { align: 'center' }, 'centered text')
// <p align="center">centered text</p>

Escaping

escape(text)

Escapes markdown special characters in untrusted input.

escape('**not bold**') // \*\*not bold\*\*

Composition

Functions return plain strings. Bold uses __ and italic uses *, so they compose without delimiter collision:

bold(italic('text'))
// __*text*__

bold(link('https://x.com', 'click'))
// __[click](https://x.com)__

blockquote(bold('important'))
// > __important__

ul([
    link('https://a.com', 'Link A'),
    link('https://b.com', 'Link B')
])
// - [Link A](https://a.com)
// - [Link B](https://b.com)

Escaping Strategy

Each function escapes only what would break its own syntax:

  • table() escapes | in cells, newlines become <br>
  • link() / image() percent-encodes (, ), spaces, and control chars in URLs
  • code() adjusts backtick delimiter length
  • HTML functions (kbd, sub, sup, details) escape <, >, &, "

Composition works without double-escaping. Use escape() for untrusted user input.