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

mdat

v3.6.5

Published

CLI tool and TypeScript library to turn comments into content in Markdown files. MDAT makes it easy to generate and update dynamic boilerplate in project readmes.

Readme

mdat

NPM Package mdat License: MIT CI Homebrew NPM Downloads mdat

CLI tool and TypeScript library to turn comments into content in Markdown files. MDAT makes it easy to generate and update dynamic boilerplate in project readmes.

Table of contents

Overview

MDAT is a CLI tool and library that uses HTML comments in Markdown files as placeholders for dynamic content. Write a comment like <!-- title -->, run mdat, and it expands into real content pulled from your project metadata. Bundled rules handle common readme sections automatically, and custom rules let you easily extend it to output almost anything.

A trivial example...

Given placeholder comments in a Markdown file like this:

some-file.md

<!-- title -->

Run your file through the tool:

mdat some-file.md

To get:

some-file.md

<!-- title -->

# mdat

<!-- /title -->

The <!-- title --> comment was expanded with content derived from your project's metadata. The rule system behind these expansions is simple to define and readily extensible.

Getting started

Dependencies

  • Node.js 24.16.0 or newer (specifically ^24.16.0 || >=26.3.0)

Written in TypeScript with bundled type definitions.

Installation

There are several ways to install mdat depending on how you're planning to use it:

CLI

Run it once without installing:

npx mdat

Or install it globally with Homebrew:

brew install kitschpatrol/tap/mdat

Or install it globally with npm:

npm install --global mdat

Library

Add it to your project to import the TypeScript API. This also puts the mdat CLI on your project's path:

npm install mdat

Features

  1. Minimalist syntax

    No screaming caps or wordy opening and closing tag keywords:

    <!-- title -->
    
    # mdat
    
    <!-- /title -->
  2. Single-comment placeholders

    Drop in a single opening comment and mdat adds the closing tag on expansion:

    <!-- title -->
  3. JSON arguments

    Pass extra data or configuration into a comment template with JSON:

    <!-- title({ prefix: "🙃" }) -->

    Arguments are parsed with JSON5, so quoting keys is optional. The bundled readme rules use Zod to validate arguments.

  4. Flexible rule system

    Rules range from a single key-value pair:

    export default { keyword: 'content' }

    To async functions:

    export default { date: () => `${new Date().toISOString()}` }

    To full rule objects with metadata:

    export default {
      date: {
        content: () => `${new Date().toISOString()}`,
        order: 1,
      },
    }

    See the bundled rules for more complex examples.

    JSON files can also be used as rule sets. MDAT flattens them so any dot-notated key path becomes a comment keyword.

  5. TypeScript native

    Rule types are exported, and configuration files can be written in TypeScript.

  6. Validation

    The mdat check command dry-runs an expansion and exits with code 1 if the file on disk has stale content.

  7. Compound rules

    Compound rules combine several individual rules into a single comment keyword.

    See <!-- header --> for an example.

  8. Polyglot metadata

    Bundled rules pull normalized metadata from almost any project type via metascope, not just package.json.

    Projects without a package manifest can provide a metadata.json file (metadata.yaml and metadata.yml also work):

    {
      "description": "A light sculpture for Disorient.",
      "keywords": ["installation", "led"],
      "repository": "https://github.com/kitschpatrol/asterism"
    }

    This is a small format for GitHub descriptions, homepages, and topics. Metascope's shared CodeMeta templates also aggregate these fields, and MDAT consumes their normalized result. Project names and richer metadata come from package manifests, codemeta.json, or other supported sources; an existing README heading remains a title fallback.

Usage

[!WARNING]

The MDAT CLI tool directly manipulates the contents of Markdown files.

Make sure any text you care about is committed before running mdat, and never directly modify content inside comment expansion blocks.

CLI

Commands

Command: mdat

Work with MDAT placeholder comments in Markdown files.

This section lists top-level commands for mdat.

If no command is provided, mdat expand is run by default.

Usage:

mdat [command] [files..] [options]

| Command | Argument | Description | | ---------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------- | | expand | [files..] [options] | Expand MDAT placeholder comments. If no files are provided, the closest readme.md is expanded. (Default command.) | | collapse | [files..] [options] | Collapse MDAT placeholder comments. If no files are provided, the closest readme.md is collapsed. | | strip | [files..] [options] | Strip MDAT comments while preserving expanded content. If no files are provided, the closest readme.md is stripped. | | check | [files..] [options] | Check if MDAT placeholder comments are up to date. Exits with code 1 if any files have stale or unexpanded content. | | create | [options] | Create a new Markdown file from a template. |

See the sections below for more information on each subcommand.

Subcommand: mdat expand

Expand MDAT placeholder comments. If no files are provided, the closest readme.md is expanded.

Usage:

mdat expand [files..] [options]

| Positional Argument | Description | Type | | ------------------- | ----------------------------------------------------------------------------------------------------- | -------- | | files | Markdown file(s) with MDAT placeholder comments. If not provided, the closest readme.md file is used. | string |

| Option | Description | Type | Default | | ------------------- | ----------------------------------------------------------------------------------------------------------------------------- | --------- | --------------------------------------------------- | | --verbose | Enable verbose logging. All verbose logs are prefixed with their log level and are printed to stderr for ease of redirection. | boolean | | | --config-c | Path(s) to additional mdat configuration files. | array | | | --output-o | Output file directory. | string | Same directory as input file. | | --name-n | Output file name. | string | Same name as input file. Overwrites the input file. | | --print | Print the expanded Markdown to stdout instead of saving to a file. Ignores --output and --name options. | boolean | | | --format-f | Format the output with Prettier. Discovers Prettier config from the file path. Requires prettier as a peer dependency. | boolean | | | --help-h | Show help | boolean | | | --version-v | Show version number | boolean | |

Subcommand: mdat collapse

Collapse MDAT placeholder comments. If no files are provided, the closest readme.md is collapsed.

Usage:

mdat collapse [files..] [options]

| Positional Argument | Description | Type | | ------------------- | ----------------------------------------------------------------------------------------------------- | -------- | | files | Markdown file(s) with MDAT placeholder comments. If not provided, the closest readme.md file is used. | string |

| Option | Description | Type | Default | | ------------------- | ----------------------------------------------------------------------------------------------------------------------------- | --------- | --------------------------------------------------- | | --verbose | Enable verbose logging. All verbose logs are prefixed with their log level and are printed to stderr for ease of redirection. | boolean | | | --output-o | Output file directory. | string | Same directory as input file. | | --name-n | Output file name. | string | Same name as input file. Overwrites the input file. | | --print | Print the expanded Markdown to stdout instead of saving to a file. Ignores --output and --name options. | boolean | | | --format-f | Format the output with Prettier. Discovers Prettier config from the file path. Requires prettier as a peer dependency. | boolean | | | --help-h | Show help | boolean | | | --version-v | Show version number | boolean | |

Subcommand: mdat strip

Strip MDAT comments while preserving expanded content. If no files are provided, the closest readme.md is stripped.

Usage:

mdat strip [files..] [options]

| Positional Argument | Description | Type | | ------------------- | ----------------------------------------------------------------------------------------------------- | -------- | | files | Markdown file(s) with MDAT placeholder comments. If not provided, the closest readme.md file is used. | string |

| Option | Description | Type | Default | | ------------------- | ----------------------------------------------------------------------------------------------------------------------------- | --------- | --------------------------------------------------- | | --verbose | Enable verbose logging. All verbose logs are prefixed with their log level and are printed to stderr for ease of redirection. | boolean | | | --output-o | Output file directory. | string | Same directory as input file. | | --name-n | Output file name. | string | Same name as input file. Overwrites the input file. | | --print | Print the expanded Markdown to stdout instead of saving to a file. Ignores --output and --name options. | boolean | | | --format-f | Format the output with Prettier. Discovers Prettier config from the file path. Requires prettier as a peer dependency. | boolean | | | --help-h | Show help | boolean | | | --version-v | Show version number | boolean | |

Subcommand: mdat check

Check if MDAT placeholder comments are up to date. Exits with code 1 if any files have stale or unexpanded content.

Usage:

mdat check [files..] [options]

| Positional Argument | Description | Type | | ------------------- | ----------------------------------------------------------------------------------------------------- | -------- | | files | Markdown file(s) with MDAT placeholder comments. If not provided, the closest readme.md file is used. | string |

| Option | Description | Type | | ------------------- | ----------------------------------------------------------------------------------------------------------------------------- | --------- | | --verbose | Enable verbose logging. All verbose logs are prefixed with their log level and are printed to stderr for ease of redirection. | boolean | | --config-c | Path(s) to additional mdat configuration files. | array | | --format-f | Format the output with Prettier. Discovers Prettier config from the file path. Requires prettier as a peer dependency. | boolean | | --help-h | Show help | boolean | | --version-v | Show version number | boolean |

Subcommand: mdat create

Create a new Markdown file from a template.

Usage:

mdat create [options]

| Option | Description | Type | Default | | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ | -------------------------------------------------------- | | --verbose | Enable verbose logging. All verbose logs are prefixed with their log level and are printed to stderr for ease of redirection. | boolean | | | --interactive-i | Run the guided interactive create process. Set explicitly to false to use default values and skip the prompt. | boolean | true | | --overwrite | Replace an existing readme file if one is found. | boolean | false, if an existing readme is found, don't touch it. | | --output-o | Output file directory. | string | Same directory as input file. | | --expand-e | Automatically run mdat immediately after creating the readme template. | boolean | true | | --template-t | Specify a template to use for the new readme. | "MDAT Readme" "Standard Readme Basic" "Standard Readme Full" | "MDAT Readme" | | --compound | Use compound comment version of the template to replace several individual comment placeholders where possible. This combines things like <!-- title -->, <!-- badges -->, etc. in a single <!-- header --> comment. It's less clutter when you're editing, but it's also less explicit. The final readme.md output is identical. | boolean | true | | --help-h | Show help | boolean | | | --version-v | Show version number | boolean | |

Examples

Expand the nearest readme
mdat
Expand a specific file
mdat your-file.md
Expand multiple files
mdat *.md
Additional config
mdat --config rules.ts more-rules.js yet-more-rules.json
Check if a file is up to date
mdat check
Collapse expanded content
mdat collapse
Strip MDAT comments from expanded content
mdat strip
Expand and format with Prettier
mdat --format
Create a starter readme from scratch
mdat create

Library

mdat exports functions for expanding, collapsing, checking, and creating Markdown files programmatically.

expand / expandString

Expands MDAT comments in one or more files. If no files are provided, auto-finds the closest readme. Writing is the caller's responsibility. Call .toString() on the returned VFile to get the result.

import { expand } from 'mdat'
import { write } from 'to-vfile'

const [file] = await expand('readme.md')
await write(file)
function expandString(
  markdown: string,
  config?: ConfigToLoad,
  options?: { format?: boolean },
): Promise<VFile>

collapse / collapseString

Removes expanded content, leaving only the opening comment placeholders. Same signatures as expand / expandString.

strip / stripString

Strips all MDAT comment tags (both opening and closing) while preserving expanded content between them. Same signatures as expand / expandString (without the config parameter, since rules are not needed).

This is useful for producing a "clean" Markdown file that doesn't need future content updates via MDAT.

check / checkString

Dry-run expand and compare with the file on disk. Returns inSync: false if the file would change. When stale, per-tag diagnostic messages are added to the result VFile identifying which specific tags are stale or unexpanded. Use reporterMdat from remark-mdat to display these messages.

create / createInteractive

function create(options?: {
  compound?: boolean
  expand?: boolean
  output?: string
  overwrite?: boolean
  template?: string
}): Promise<string>

Creates a new readme from a bundled template. createInteractive runs the same flow with interactive prompts.

loadConfig

function loadConfig(options?: {
  additionalConfig?: ConfigToLoad
  defaults?: Config
  searchFrom?: string
}): Promise<Config>

Discovers and loads configuration via cosmiconfig, merges with defaults and additional config, and returns a validated Config object. Useful for advanced use cases or passing into remark-mdat directly.

Configuration

Configuration is defined in config files discovered automatically by cosmiconfig, or provided explicitly via --config.

TypeScript or JavaScript configuration files are recommended. The file default-exports a Config record:

// Your mdat.config.ts
import { defineConfig } from 'mdat'

export default defineConfig({
  date: {
    content: () => new Date().toISOString(),
    order: 1,
  },
  greeting: 'Hello, world!',
})

The configuration file may be located in any cosmiconfig search location. mdat.config.ts in the project root is the most common choice.

Configuration in package.json

Shared configurations can be specified in package.json by passing a string that resolves to a module with a default Config export:

{
  "mdat": "@kitschpatrol/mdat-config"
}

Rules can also be defined directly in package.json:

{
  "mdat": {
    "what": "hath god wrought"
  }
}

[!NOTE]

mdat also searches for and merges any ambient Remark .remarkrc configuration files. This is unrelated to mdat rules, but it can affect how Markdown is rendered.

Config format

A config is a record whose keys become comment keywords:

type Config = Record<string, Rule>

type Rule =
  | ((options: JsonValue, context: RuleContext) => Promise<string> | string)
  | Rule[]
  | string
  | {
      content:
        ((options: JsonValue, context: RuleContext) => Promise<string> | string) | Rule[] | string
      order?: number
    }

type RuleContext = {
  filePath: string | undefined
  frontmatter: Record<string, unknown> | undefined
  tree: Root
}

Simple rules are strings or functions on the key. For metadata like processing order, use the object form with a content key. The content value can be an array of Rule objects for compound rules.

When multiple config files are loaded, they are merged. CLI --config takes precedence over ambient configuration, and the last rule for a given key wins.

Creating custom rules

See the Examples section of the remark-mdat readme, or look at the bundled rules for complex examples.

Bundled rules

Every stand-alone rule that generates a section of the readme emits its own heading, and accepts two options to control it:

  • heading is true by default, which emits the rule's standard heading. Pass a string to replace the heading text, or false to leave the heading out, e.g. when you've written your own heading above the comment, or when the same rule appears several times in one section.

  • headingLevel (1-6) sets the heading's Markdown level. Any sub-headings are nested one level deeper, whether or not the heading itself is shown. The defaults match each rule's placement in the bundled MDAT Readme template: 2 for top-level sections (table-of-contents, contributing, license, and skills), and 3 for the rules that sit under "Getting started" (dependencies, install, and development-dependencies).

<!-- install({ heading: "Setup", headingLevel: 2 }) -->

Rules that generate inline content, like badges, code, and size, have no heading. Rule plugins follow the same convention.

Stand-alone

  • <!-- title -->

    The project name, derived from project metadata.

  • <!-- banner -->

    Looks for an image in the project directory for use as a banner. Searches for typical names and formats.

  • <!-- badges -->

    Generates badges based on project metadata. Each badge has an option. Leave it out for the default behavior, pass false to hide the badge, or pass true to require it, which reports an error if the metadata the badge needs is missing. Two options also take a value that replaces the detected metadata.

    | Option | Badge | Default | Value | | ------------------- | ------------------------------------------------------------------------------------ | ----------------------------------------------------------------------- | ----------------------------------------------- | | npm | NPM version, for each package | Shown for public packages | Package names (string[]), useful in monorepos | | license | License | Shown when a license file matches a known SPDX license | | | ci | GitHub Actions CI status | Shown when a workflow is named "CI" | | | gitLfs | Git LFS | Shown when the repository tracks files with Git LFS | | | vscode | VS Code Marketplace version | Shown for VS Code extensions hosted on GitHub | | | homebrew | Homebrew tap | Shown when a brewpub --tap invocation is found in the package scripts | Formula ("owner/tap/formula") | | githubRelease | Latest GitHub release | Hidden | | | npmDownloads | Monthly NPM downloads, for each package | Hidden | | | obsidianDownloads | Obsidian community plugin downloads | Hidden | | | bundleSize | Bundlephobia minified and gzipped size, for each package | Hidden | |

    Badges appear in the order of the table, followed by custom badges, which take a Record<string, { image: string, link: string }> keyed by alt text.

    The download and size badges cover the packages listed in npm, or the current package if it's public, even when npm is false. VS Code extensions are identified by the publisher and engines.vscode fields in package.json. Shields.io retired its Marketplace badge, so the version is read from package.json in the GitHub repository instead, respecting repository.directory in monorepos. The Homebrew formula is detected the same way as in the install rule. The GitHub release badge needs a GitHub repository URL, and the Obsidian downloads badge reads the plugin ID from manifest.json.

    <!-- badges({ ci: false, npmDownloads: true }) -->
  • <!-- description -->

    The project description. Also aliased as <!-- short-description --> for standard-readme compatibility.

  • <!-- install -->

    Install instructions derived from package.json, written for people consuming the package rather than developing it, so they always use npm. Packages with a bin get CLI instructions (npx, Homebrew, and npm install --global), packages with an entry point (exports, main, module, or types) get library instructions (npm install), and packages with both get a section for each. The Homebrew option appears automatically when a brewpub --tap invocation is found in the package scripts, and can be set explicitly with homebrew: "owner/tap/formula" or suppressed with homebrew: false. Pass dev: true to recommend npm install --save-dev for tooling packages. Private packages are rejected. Falls back to pip, cargo, gem, or go install for Python, Rust, Ruby, and Go projects. The optional headingLevel sets the Markdown heading level and defaults to 3, matching the template's placement under a "Getting started" section:

    <!-- install({ headingLevel: 2, dev: true, homebrew: false }) -->

    Also aliased as <!-- installation -->.

  • <!-- dependencies -->

    Documents platform requirements and peer dependencies. Lists runtime platforms (Node, Python, Rust, Go, Ruby, etc.) with version constraints from engines or equivalent metadata, supported operating systems, and required peer dependencies with links to npm. Optional peer dependencies are left out. Version ranges are described in plain language (>=24.16.0 becomes "24.16.0 or newer"), with the exact range included when it can't be summarized that simply. The optional headingLevel sets the Markdown heading level and defaults to 3.

  • <!-- development-dependencies -->

    Documents the tools needed to work on the project itself, derived from the devEngines and packageManager fields in package.json. Lists required runtimes and package managers with links and version constraints. The optional headingLevel sets the Markdown heading level and defaults to 3. Also aliased as <!-- dev-dependencies -->.

  • <!-- table-of-contents -->

    Auto-generated via mdast-util-toc. The optional depth limits how deeply nested headings are listed and defaults to 3. Also aliased as <!-- toc -->.

  • <!-- contributing -->

    Invites issues and pull requests with links derived from project metadata. Mentions @kitschpatrol/shared-config only when package.json lists it as a dependency.

  • <!-- license -->

    Documents the project license.

  • <!-- code({ file: "./file.ts" }) -->

    Embeds a code block from elsewhere in your repository.

  • <!-- size({ file: "./package.json" }) -->

    Embeds a file's size, with optional Brotli or Gzip compressed size.

  • <!-- size-table({ files: [".gitignore", "license.txt"] }) -->

    A table of files and their compressed sizes. The optional precision sets the maximum number of decimal places and defaults to 0:

    | File | Original | Gzip | Brotli | | ----------- | -------- | ----- | ------ | | .gitignore | 365 B | 279 B | 252 B | | license.txt | 1 kB | 659 B | 468 B |

  • <!-- skills -->

    Documents the Agent Skills bundled with the project and how to install them. Detects skills from skills/<name>/SKILL.md files, and gives each one a "Skill:" sub-heading linking to its SKILL.md file, followed by the first sentence of the description from its frontmatter. Publishable npm packages get instructions for syncing skills from the installed package with the experimental_sync command of Vercel's skills CLI, and for installing them from the repository with skills add. Private packages and non-Node projects only get the repository instructions. Reports an error if no skills are found. Remember to include the skills directory in the files field of package.json. The optional headingLevel sets the Markdown heading level and defaults to 2.

Compound

Compound rules combine several stand-alone rules under a single keyword. Options are passed as an array with one entry per stand-alone rule, in order, e.g. <!-- footer([{ headingLevel: 3 }, { heading: "Legal", headingLevel: 3 }]) -->.

  • <!-- header -->

    Combines rules commonly applied at the top of a readme:

    <!-- title -->
    <!-- banner -->
    <!-- badges -->
    <!-- shortDescription -->
  • <!-- footer -->

    Combines rules commonly applied at the end:

    <!-- contributing -->
    <!-- license -->

Bundled templates

The create command provides starter readme templates:

  • MDAT Readme — An expansive starting point. The readme in this repo was started from this template.
  • Standard Readme basic — Only the "required" sections from the Standard Readme spec.
  • Standard Readme full — All sections from the Standard Readme spec.

If the project has agent skills in skills/<name>/SKILL.md, a <!-- skills --> placeholder is added after the template's "Usage" section.

Plugins

Rule plugins are packages for sharing mdat expansion rules across projects.

Installing a rule plugin

npm install mdat-plugin-example

Spread the plugin into your configuration:

// Your mdat.config.ts
import { defineConfig } from 'mdat'
import example from 'mdat-plugin-example'

export default defineConfig({
  ...example,
})

Then use the rule in Markdown:

<!-- example -->

And expand:

mdat

Creating a rule plugin

A rule plugin is an ESM module with a default export of Config. By convention, use the mdat-plugin- name prefix.

import type { Config } from 'mdat'

export default {
  hello: {
    content() {
      return 'Hello from an mdat plugin!'
    },
  },
} satisfies Config

See mdat-plugin-example for a complete example.

Available rule plugins

mdat-plugin-tldraw

Embed tldraw files in your readme.

Example: <!-- tldraw({ src: "./sketch.tldr" }) -->

mdat-plugin-cli-help

Transform a CLI command's --help output into Markdown tables. Recursively calls --help on subcommands. Currently parses Yargs, Meow, and Commander help output formats, falling back to a plain text code block as necessary.

Example: <!-- cli-help -->

mdat-plugin-api-help

Generate API documentation for a TypeScript package's public exports. Runs TypeDoc over the package's entry point and embeds signatures, JSDoc descriptions, parameter and property tables, and examples as Markdown. A compact format renders one table row per export for large or namespaced APIs, and exports can be selected by name.

Example: <!-- api-help({ format: "compact", include: ["sync*"] }) -->

Agent skills

This project bundles 2 Agent Skills in its published package to help coding agents work with mdat.

To sync the skills into your project, run Vercel's skills CLI from your project root:

npx skills experimental_sync

Or install globally:

npx skills add kitschpatrol/mdat --global

Included skills:

Skill: mdat

Use and configure mdat, the Markdown Autophagic Template CLI and library that expands HTML comment placeholders such as <!-- title --> in Markdown files.

Skill: mdat-plugin-authoring

Write custom mdat expansion rules and publishable mdat rule plugins (mdat-plugin-* packages).

Migrating from 1.x to 2.x or 3.x

The 2.0 version introduces significant breaking changes in the interest of simplicity and a somewhat narrowed scope of concerns. 3.0 was a mostly non-breaking, only dropping support for Node 22.

Plugin compatibility was broken from 1.x to 2.x, but maintained in 3.x.

Details of the changes and migration strategies are enumerated below.

Flat CLI commands

The mdat readme subcommand is gone. All commands are now top-level:

| v1 | v2 | | ------------------- | --------------- | | mdat readme | mdat | | mdat readme init | mdat create | | mdat readme check | mdat check | | mdat expand | mdat expand | | mdat collapse | mdat collapse | | no equivalent | mdat strip |

Running mdat with no arguments expands the closest readme, matching the behavior of the 1.x mdat readme command.

Polyglot metadata

Readme rules no longer read from package.json directly. Instead, mdat uses metascope to aggregate metadata across ecosystems (Node, Python, Rust, Go, Ruby, etc.). Rules like <!-- title -->, <!-- description -->, and <!-- license --> now work in non-Node projects.

Simplified configuration

The v1 Config type (which had fields like addMetaComment, assetsPath, closingPrefix, etc.) is gone. Configuration files now export a flat Config record of rules directly:

// Mdat.config.ts (v2)
import { defineConfig } from 'mdat'

export default defineConfig({
  hello: 'world',
})

The --assets, --package, --meta, --prefix, and --rules CLI options have been removed. Use --config (-c) to provide additional config files.

Stricter argument syntax

In 2.x, arguments must use function-call syntax with parentheses

<!-- greeting({name: 'Alice'}) -->

New functionality

  • The new --format flag runs expanded output through Prettier with local configuration before writing.
  • The badges rule now detects GitHub Actions CI workflows and includes a CI status badge automatically.
  • Check command reimplemented as a simplified dry-run expand and diff and reporting if content is unexpanded or out of date. Respects the --format flag.

Rule shape changes

See the remark-mdat project readme for details on lower-level changes to how rules are defined and structured.

Background

Motivation

A package definition file like package.json is the canonical source of truth for a project's metadata, yet fragments of it end up duplicated in the readme. Keeping them in sync is tedious.

MDAT solves this by turning HTML comments in Markdown into placeholders for dynamic content. Run mdat and the comments expand with content pulled from your project metadata. The file is updated in place.

Similar projects

There's quite a bit of prior art and similar explorations of this problem space:

  • Benjamin Lupton's projectz
    Goes way back.

  • David Wells' Markdown Magic
    I somehow missed the existence of this one until after building out MDAT. It's very similar conceptually, and has a nice ecosystem of plugins.

  • Titus Wormer's mdast-zone
    Allows comments to be used as ranges or markers in Markdown files. Similar tree parsing and walking strategy to MDAT. Mdast-zone uses different syntax for arguments, and requires both opening and closing tags to be present for expansion to occur.

  • Jason Dent's inject-markdown

  • lillallol's md-in-place

  • AutoMD
    Extremely similar functionality to mdat. The project was initiated around the same time as MDAT, but I didn't find the project until a few years later. Ships passing in the night.

  • Franck Abgrall's readme-md-generator

  • Anders Pitman's tuplates

  • VitePress' Markdown file inclusion

  • Hiroki Osame's comment-mark

  • Hiroki Osame's mdeval
    The best name!

Implementation notes

This project was split from a monorepo containing both mdat and remark-mdat into separate repos in July 2024.

The name "MDAT" is an acronym for the "Markdown Autophagic Template" system, so named because of the circularity of a templating system where the input file is also the output file.

Run pnpm bench to compare performance against test/benchmarks/baseline.json. Run pnpm bench:baseline to refresh the saved results using Vitest 5's benchmark format. Both commands support filters such as -t loadConfig; refreshing a filtered subset preserves the other baselines. Use the same machine and Node.js version for meaningful comparisons.

Maintainers

kitschpatrol

Acknowledgments

Contributing

Issues are welcome and appreciated.

Please open an issue to discuss changes before submitting a pull request. Unsolicited PRs (especially AI-generated ones) are unlikely to be merged.

This repository uses @kitschpatrol/shared-config (via its ksc CLI) for linting and formatting, plus MDAT for readme placeholder expansion.

License

MIT © Eric Mika