mdat
v2.3.2
Published
CLI tool and TypeScript library implementing the Markdown Autophagic Template (MDAT) system. MDAT lets you use comments as dynamic content templates in Markdown files, making it easy to generate and update readme boilerplate.
Downloads
2,260
Maintainers
Readme
mdat
CLI tool and TypeScript library implementing the Markdown Autophagic Template (MDAT) system. MDAT lets you use comments as dynamic content templates in Markdown files, making it easy to generate and update readme boilerplate.
Table of contents
- Overview
- Getting started
- Features
- Usage
- Plugins
- Migrating from 1.x to 2.x
- Background
- Maintainers
- Acknowledgments
- Contributing
- License
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.mdTo 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 22+ (specifically >=22.17.0). Written in TypeScript with bundled type definitions.
Installation
Install locally to access the CLI and API in a single project:
pnpm install mdatOr install globally:
pnpm install --global mdatFeatures
Minimalist syntax
No screaming caps or wordy opening and closing tag keywords:
<!-- title --> # mdat <!-- /title -->Single-comment placeholders
Drop in a single opening comment and
mdatadds the closing tag on expansion:<!-- title -->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.
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.
TypeScript native
Rule types are exported, and configuration files can be written in TypeScript.
Validation
The
mdat checkcommand dry-runs an expansion and exits with code 1 if the file on disk has stale content.Compound rules
Compound rules combine several individual rules into a single comment keyword.
See
<!-- header -->for an example.Polyglot metadata
Bundled rules pull normalized metadata from almost any project type via metascope, not just
package.json.
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
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
mdatExpand a specific file
mdat your-file.mdExpand multiple files
mdat *.mdAdditional config
mdat --config rules.ts more-rules.js yet-more-rules.jsonCheck if a file is up to date
mdat checkCollapse expanded content
mdat collapseStrip MDAT comments from expanded content
mdat stripExpand and format with Prettier
mdat --formatCreate a starter readme from scratch
mdat createAPI
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 no longer depends on MDAT for future updates.
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]
mdatalso searches for and merges any ambient Remark.remarkrcconfiguration 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
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. Supports NPM version, license, and CI status badges.
<!-- description -->The project description. Also aliased as
<!-- short-description -->for standard-readme compatibility.<!-- install -->Ecosystem-aware install instructions derived from project metadata. Emits
pnpm add/npm installfor Node packages (plus apnpx/npxhint when the project exposes a binary), and falls back topip,cargo,gem, orgo installfor Python, Rust, Ruby, and Go projects.<!-- dependencies -->Documents platform requirements and peer dependencies. Lists runtime platforms (Node, Python, Rust, Go, Ruby, etc.) with version constraints from
enginesor equivalent metadata, supported operating systems, and peer dependencies with links to npm.<!-- table-of-contents -->Auto-generated via mdast-util-toc. Also aliased as
<!-- toc -->.<!-- contributing -->Invites issues and pull requests with links derived from project metadata.
<!-- 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:
| File | Original | Gzip | Brotli | | ----------- | -------- | ----- | ------ | | .gitignore | 318 B | 252 B | 237 B | | license.txt | 1 kB | 659 B | 468 B |
Compound
Compound rules combine several stand-alone rules under a single keyword.
<!-- 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.
Plugins
Rule plugins are packages for sharing mdat expansion rules across projects.
Installing a rule plugin
pnpm install mdat-plugin-exampleSpread 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:
mdatCreating 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 ConfigSee 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 and Meow output formats, falling back to a plain text code block as necessary.
Example: <!-- cli-help -->
Migrating from 1.x to 2.x
The 2.0 version introduces significant breaking changes in the interest of simplicity and a somewhat narrowed scope of concerns.
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 |
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
--formatflag 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
--formatflag.
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 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
Implementation notes
This project was split from a monorepo containing both mdat and remark-mdat into separate repos in July 2024.
Maintainers
Acknowledgments
The unified, remark, and unist / mdast ecosystem does the heavy lifting of Markdown parsing and transformation.
Richard Litt's Standard Readme specification inspired the bundled templates.
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.
