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.
Maintainers
Readme
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
- Getting started
- Features
- Usage
- Plugins
- Agent skills
- Migrating from 1.x to 2.x or 3.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.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 mdatOr install it globally with Homebrew:
brew install kitschpatrol/tap/mdatOr install it globally with npm:
npm install --global mdatLibrary
Add it to your project to import the TypeScript API. This also puts the mdat CLI on your project's path:
npm install 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.Projects without a package manifest can provide a
metadata.jsonfile (metadata.yamlandmetadata.ymlalso 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
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 createLibrary
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]
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
Every stand-alone rule that generates a section of the readme emits its own heading, and accepts two options to control it:
headingistrueby default, which emits the rule's standard heading. Pass a string to replace the heading text, orfalseto 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:2for top-level sections (table-of-contents,contributing,license, andskills), and3for the rules that sit under "Getting started" (dependencies,install, anddevelopment-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
falseto hide the badge, or passtrueto 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 abrewpub --tapinvocation 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
custombadges, which take aRecord<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 whennpmisfalse. VS Code extensions are identified by thepublisherandengines.vscodefields inpackage.json. Shields.io retired its Marketplace badge, so the version is read frompackage.jsonin the GitHub repository instead, respectingrepository.directoryin monorepos. The Homebrew formula is detected the same way as in theinstallrule. The GitHub release badge needs a GitHub repository URL, and the Obsidian downloads badge reads the plugin ID frommanifest.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 usenpm. Packages with abinget CLI instructions (npx, Homebrew, andnpm install --global), packages with an entry point (exports,main,module, ortypes) get library instructions (npm install), and packages with both get a section for each. The Homebrew option appears automatically when abrewpub --tapinvocation is found in the package scripts, and can be set explicitly withhomebrew: "owner/tap/formula"or suppressed withhomebrew: false. Passdev: trueto recommendnpm install --save-devfor tooling packages. Private packages are rejected. Falls back topip,cargo,gem, orgo installfor Python, Rust, Ruby, and Go projects. The optionalheadingLevelsets the Markdown heading level and defaults to3, 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
enginesor 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.0becomes "24.16.0 or newer"), with the exact range included when it can't be summarized that simply. The optionalheadingLevelsets the Markdown heading level and defaults to3.<!-- development-dependencies -->Documents the tools needed to work on the project itself, derived from the
devEnginesandpackageManagerfields inpackage.json. Lists required runtimes and package managers with links and version constraints. The optionalheadingLevelsets the Markdown heading level and defaults to3. Also aliased as<!-- dev-dependencies -->.<!-- table-of-contents -->Auto-generated via mdast-util-toc. The optional
depthlimits how deeply nested headings are listed and defaults to3. Also aliased as<!-- toc -->.<!-- contributing -->Invites issues and pull requests with links derived from project metadata. Mentions @kitschpatrol/shared-config only when
package.jsonlists 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
precisionsets the maximum number of decimal places and defaults to0:| 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.mdfiles, and gives each one a "Skill:" sub-heading linking to itsSKILL.mdfile, followed by the first sentence of the description from its frontmatter. Publishable npm packages get instructions for syncing skills from the installed package with theexperimental_synccommand of Vercel's skills CLI, and for installing them from the repository withskills add. Private packages and non-Node projects only get the repository instructions. Reports an error if no skills are found. Remember to include theskillsdirectory in thefilesfield ofpackage.json. The optionalheadingLevelsets the Markdown heading level and defaults to2.
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-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, 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_syncOr install globally:
npx skills add kitschpatrol/mdat --globalIncluded 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
--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 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
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.
