@amritk/generate-markdown renders a configuration reference table from a config.schema.json file and writes the result to README.md. It exists so the documentation for a CLI's flags can be regenerated from the schema itself, keeping the two in sync.
For every property it reads the standard JSON Schema keywords:
type— shown in the Type columndescription— the first paragraph fills the full-width detail rowdefault— shown (quoted/JSON-encoded) in the Default columnenum— listed as Allowed: values in the detail rowexamples— listed as Examples: values in the detail rowrequired— the parent'srequiredarray drives the Required column
Schemas built from references work too. $ref pointers are resolved against the
document's $defs (any #/… JSON pointer, in fact) before rendering, recursive
definitions are detected and collapsed so generation always terminates, and
sibling keywords on a $ref node — typically description — override the
referenced definition. When a property describes its type through enum,
const, or anyOf/oneOf/allOf rather than a plain type, the Type
column shows an inferred label (e.g. string or number | string).
…plus two non-standard keywords for richer output:
x-cli-flag— the matching CLI flag (e.g.--schema <path>), shown in the CLI Flag columnx-icon— an emoji shown next to the property name
Columns and icons are only rendered when the schema actually uses them. The
CLI Flag, Required, and Default columns are each dropped entirely
when no property anywhere in the schema fills them, and a property with no
x-icon simply shows no icon. There are no — placeholders: a cell with
nothing to say is left empty. The check spans the whole schema (including
nested objects), so every table keeps the same set of columns.
Object properties with their own properties are linked to a nested detail table rendered below the main one.
npm install @amritk/generate-markdown
# or
pnpm add @amritk/generate-markdown
# or
yarn add @amritk/generate-markdown
# or
bun add @amritk/generate-markdownimport { generateMarkdown } from '@amritk/generate-markdown'
await generateMarkdown()
// Reads ./config.schema.json from process.cwd()
// Writes ./README.mdIf README.md already exists and contains the marker comments below, only the content between them is replaced — everything else in the file is preserved:
<!-- config-table-start -->
<!-- config-table-end -->When there is no README.md yet, one is created holding just the table. When a README.md exists but is missing either marker, generateMarkdown throws rather than overwriting it — add both markers where the table should go and re-run.
Each example below shows an input config.schema.json and the markdown generateMarkdown() produces from it.
Defaults are rendered in the Default column. Strings are quoted; numbers and booleans are printed bare; objects and arrays are JSON-encoded. None of these properties use a CLI flag or are required, so those columns are dropped — only Property, Type, and Default remain.
Input schema
{
"title": "Defaults",
"properties": {
"outDir": { "type": "string", "default": "./generated", "x-icon": "📁", "description": "Output directory." },
"port": { "type": "number", "default": 8080, "x-icon": "🔌", "description": "Port to listen on." },
"minify": { "type": "boolean", "default": false, "x-icon": "🗜️", "description": "Minify the output." },
"include": { "type": "array", "default": ["**/*.ts"], "x-icon": "📥", "description": "Glob patterns to include." },
"env": { "type": "object", "default": { "NODE_ENV": "production" }, "x-icon": "🌱", "description": "Environment variables." }
}
}Generated markdown:
| Property | Type | Default |
|---|---|---|
📁 outDir |
string |
"./generated" |
| Output directory. | ||
🔌 port |
number |
8080 |
| Port to listen on. | ||
🗜️ minify |
boolean |
false |
| Minify the output. | ||
📥 include |
array |
["**/*.ts"] |
| Glob patterns to include. | ||
🌱 env |
object |
{"NODE_ENV":"production"} |
| Environment variables. | ||
enum becomes an Allowed: line and examples becomes an Examples: line, both appended to the property's detail row beneath the description.
Input schema
{
"title": "Input",
"required": ["input"],
"properties": {
"input": {
"type": "string",
"enum": ["json", "zod", "typebox"],
"default": "json",
"x-cli-flag": "--input <format>",
"x-icon": "🔌",
"description": "Source format of the schema.",
"examples": ["json", "zod"]
}
}
}Generated markdown:
| Property | CLI Flag | Type | Required | Default |
|---|---|---|---|---|
🔌 input |
--input <format> |
string |
✅ | "json" |
| Source format of the schema. Allowed: "json", "zod", "typebox"Examples: "json", "zod" |
||||
A property name appears in the Required column (✅) when it is listed in the object's required array. x-cli-flag fills the CLI Flag column, and x-icon sits next to the name. Neither property sets a default, so the Default column is dropped; the outFile row, which isn't required, leaves the Required cell empty rather than printing a placeholder.
Input schema
{
"title": "RequiredFlags",
"required": ["schema"],
"properties": {
"schema": { "type": "string", "x-cli-flag": "--schema <path>", "x-icon": "📄", "description": "Path to the schema to process.", "examples": ["./schema.json"] },
"outFile": { "type": "string", "x-cli-flag": "--out-file <file>", "x-icon": "📄", "description": "Write everything to a single file." }
}
}Generated markdown:
| Property | CLI Flag | Type | Required |
|---|---|---|---|
📄 schema |
--schema <path> |
string |
✅ |
| Path to the schema to process. Examples: "./schema.json" |
|||
📄 outFile |
--out-file <file> |
string |
|
| Write everything to a single file. | |||
An object property that declares its own properties is linked to a detail table rendered below the main one. The nested table uses the object's own required array, so required markers are scoped to each level.
Input schema
{
"title": "Nested",
"properties": {
"server": {
"type": "object",
"x-icon": "🖥️",
"description": "HTTP server settings.",
"required": ["host"],
"properties": {
"host": { "type": "string", "x-icon": "🌐", "description": "Hostname to bind." },
"port": { "type": "number", "x-icon": "🔌", "default": 3000, "description": "Port to listen on." }
}
}
}
}Generated markdown:
| Property | Type | Required | Default |
|---|---|---|---|
🖥️ server |
object |
||
| HTTP server settings. | |||
| Property | Type | Required | Default |
|---|---|---|---|
🌐 host |
string |
✅ | |
| Hostname to bind. | |||
🔌 port |
number |
3000 |
|
| Port to listen on. | |||
$ref pointers are inlined from $defs before rendering, so a schema assembled
from reusable definitions produces the same tables as an inline one. Sibling
keywords on a $ref (here x-icon and description) override the definition,
and a property whose type comes from enum (logLevel) gets an inferred Type.
Input schema
{
"title": "Refs",
"required": ["server"],
"properties": {
"server": { "$ref": "#/$defs/server", "x-icon": "🖥️", "description": "HTTP server settings." },
"logLevel": { "$ref": "#/$defs/logLevel", "x-icon": "🔊" }
},
"$defs": {
"server": {
"type": "object",
"required": ["host"],
"properties": {
"host": { "type": "string", "x-icon": "🌐", "description": "Hostname to bind." },
"port": { "type": "number", "x-icon": "🔌", "default": 3000, "description": "Port to listen on." }
}
},
"logLevel": {
"enum": ["debug", "info", "warn", "error"],
"default": "info",
"description": "Minimum log level to emit."
}
}
}Generated markdown:
| Property | Type | Required | Default |
|---|---|---|---|
🖥️ server |
object |
✅ | |
| HTTP server settings. | |||
🔊 logLevel |
string |
"info" |
|
| Minimum log level to emit. Allowed: "debug", "info", "warn", "error" |
|||
| Property | Type | Required | Default |
|---|---|---|---|
🌐 host |
string |
✅ | |
| Hostname to bind. | |||
🔌 port |
number |
3000 |
|
| Port to listen on. | |||
No arguments. Reads from ${cwd}/config.schema.json and writes to ${cwd}/README.md.
@amritk/mjst— uses this package to keep its README's flag table in sync withconfig.schema.json@amritk/generate-parsers— sibling generator for TypeScript parsers and types@amritk/generate-validators— sibling generator for predicate validators