Skip to content

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

README.md

@amritk/generate-markdown

Generate markdown documentation from JSON Schemas.

status  version  license  JSON Schema  node  vibe coded


Overview

@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 column
  • description — the first paragraph fills the full-width detail row
  • default — shown (quoted/JSON-encoded) in the Default column
  • enum — listed as Allowed: values in the detail row
  • examples — listed as Examples: values in the detail row
  • required — the parent's required array 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 column
  • x-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.


Installation

npm install @amritk/generate-markdown
# or
pnpm add @amritk/generate-markdown
# or
yarn add @amritk/generate-markdown
# or
bun add @amritk/generate-markdown

Usage

import { generateMarkdown } from '@amritk/generate-markdown'

await generateMarkdown()
// Reads ./config.schema.json from process.cwd()
// Writes ./README.md

If 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.


Examples

Each example below shows an input config.schema.json and the markdown generateMarkdown() produces from it.

Defaults of every type

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.

Enums and examples

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"

Required properties and CLI flags

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.

Nested objects

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.

server

Property Type Required Default
🌐 host string
Hostname to bind.
🔌 port number 3000
Port to listen on.

References and definitions

$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"

server

Property Type Required Default
🌐 host string
Hostname to bind.
🔌 port number 3000
Port to listen on.

API

generateMarkdown(): Promise<void>

No arguments. Reads from ${cwd}/config.schema.json and writes to ${cwd}/README.md.


Related packages


License

MIT