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

@firefoxic/beautypography

v0.1.1

Published

Binds function words, numbers and em dashes in Markdown prose with non-breaking spaces

Readme

@firefoxic/beautypography

License: MIT Changelog Test Status

Binds function words, numbers and em dashes in Markdown prose with non-breaking spaces, so that a line break never leaves an article, a preposition or a dash stranded at the end of a line.

It reads Markdown as Markdown: inline code spans and fenced code blocks are left exactly as their author wrote them, down to the spaces inside them.

The package has no runtime dependencies, and is not going to have any.

Installation

Globally

pnpm add -g @firefoxic/beautypography

Locally

pnpm add -D @firefoxic/beautypography

Node 24.0 or newer.

Usage

On the command line

beautypography [options] [file...]

With no file given, every Markdown file below the current directory is bound, skipping the directories that belong to a tool rather than to the prose — .claude, .git, coverage, dist, node_modules and tmp, wherever in the tree they stand — and leaving the LICENSE.md of the root alone, since a license is quoted verbatim.

A file given by name is bound whatever it is called and wherever it lies: naming a file is asking for that file.

| Option | What it does | |--------|--------------| | -c, --check | Report the files that need binding instead of writing them | | -h, --help | Print the usage | | -v, --version | Print the version |

The command answers with 0 when there was nothing to do or everything was written, 1 when --check found prose that needs binding or an option was not understood, and 2 when a file could not be read or written. What went wrong is written to the error stream; what was bound, to the standard one.

As a library

import { bindProse } from "@firefoxic/beautypography"

let bound = bindProse("Read the docs of a kind — and more.")

bindProse(source, language?) applies the convention to a whole document and is what the command uses. It is idempotent: binding a bound document changes nothing.

For a single line, bindLine(line, patterns) takes a language already compiled by createPatterns(language) — compiling once and binding many lines is why the command is fast on a large document.

ENGLISH is the only language that ships. toWords splits a list of words written as indented prose, which is how the language file holds them. NBSP is the character everything above puts in.

The types Language, Patterns and MaskedLine are exported for anyone writing a language of their own.

What gets bound

  • Function words bind forward to the word that follows: articles, prepositions, coordinating and subordinating conjunctions, relative pronouns, particles, and numerals spelled out. Emphasis markers stay glued to the word, so **not** ready and _not_ ready bind as the word does.
  • Phrases and names claim their spaces before any single word can, longest first, and only where they stand whole: Keep a Changelog is bound, while the opening of Keep a Changelogs is not. A name is matched as it is written, since the case of a name carries meaning.
  • Numbers are bound on both sides, and never to a function word: cost 0.05 ms keeps its measurement whole, version 2 of it keeps its version and lets the of go, and the 5 of the 5 files goes to the files rather than to the article.
  • Em dashes bind to the word before them, never to the line below.
  • Exceptions are unbound again at the end, because the rules bind them but the meaning does not — that is and its like.

Keeping prose bound

In a pre-commit hook

beautypography --check $(git diff --cached --name-only --diff-filter=ACM | grep -E '\.md$')

In CI

- name: Check the prose
  run: pnpm dlx @firefoxic/beautypography --check

Known limits

  • English only. A second language is planned, and the matching logic is ready for one, but nothing else ships yet.
  • The word list is incomplete. It covers the function words that break lines most visibly, not every one English has.
  • A code block indented by four spaces is prose to this tool. Fenced blocks are recognised the way CommonMark writes them — backticks or tildes, any length, under an indent of up to three spaces — but telling an indented code block from a nested list item needs a full block parser.
  • Any word before a number takes it. see 5 files binds the 5 back to see as well as forward to the files it counts. Telling a verb from a version needs to know parts of speech.