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

sho-gen

v0.2.0

Published

Declarative model-driven code generation pipeline for JSON/YAML models — JSONata model-to-model and EJS model-to-text rules in one config

Readme

sho-gen

npm CI license node

Declarative, deterministic code & config generation from JSON/YAML models. One config describes your models and rules — sho-gen generates code, config files, docs, or transformed models. Non-interactive, schema-validated, byte-identical on every run.

# team.gen.yaml
models:
  - name: team
    json:
      - { id: ada, name: Ada Lovelace, role: Pioneer }
      - { id: grace, name: Grace Hopper, role: Admiral }
rules:
  - input: team                              # array ⇒ one output PER item
    template: "# <%= name %>\n\nRole: <%= role %>"
    output: { file: "pages/<%= id %>.md" }
$ shogen team.gen.yaml -o out
  -> out/pages/ada.md
  -> out/pages/grace.md

The same mechanics scale from this to real pipelines — see api-scaffold: one data model → TypeScript types

  • SQL schema + HTML forms.

Features

  • JSON or YAML configuration defining
    • model sources: inline JSON, inline YAML, or files (.json, .yaml, .yml)
    • rules with
      • an input: a model name or a JSONata expression over the model container
      • a template: EJS (string, array of lines, or file), a JSONata expression (model-to-model transformation), or a structural construct tree with $let/$if/$each/$firstOf/$lit and the decision directives $decide (exhaustive tables), $pick (preference cascades) and $dispatch (exhaustive type dispatch)
      • an output: a file (path is EJS-interpolated per item) or a model path inside the container
  • array inputs fan out: one rule generates one output per array item (multi-file generation from a single template)
  • rule outputs written back into the model container can feed later rules (transformation pipelines)
  • JSON-Schema validation of every generator configuration (schema/gen.schema.json)
  • regeneration merge on file outputs (designer-owned elements survive), --dry-run with unified diffs, and a per-run manifest with a deterministic hash
  • per-rule bind: context and a small JSONata helper stdlib ($uppercaseFirst, $kebab, ..., $json)
  • output sandbox: generated files can never escape the configured output directory
  • extensible handler registry (model / template / output handlers can be registered programmatically)

Why shogen?

shogen fills a gap between three tool families (see docs/competitive-landscape.md for the full analysis):

  • Scaffolders (plop, hygen, Yeoman) are interactive and prompt-driven — they have no notion of a data model, no transformation pipelines, and their logic lives in code (plopfiles, generator classes). shogen is non-interactive and driven entirely by a declarative config.
  • Data-templating / config languages (Jsonnet, ytt, CUE) transform data with their own functional languages, but have no rule model, no template-per-array-item fan-out (an open feature request in ytt), and no code-generation focus.
  • Model-driven engineering tools (Telosys, Acceleo, Xtend) are Java/Eclipse-centric, use their own model DSLs, and keep generation logic in template code — none is a config-driven pipeline on plain JSON/YAML models in the npm ecosystem.

What no active tool offers in combination — and shogen does:

  1. one declarative JSON/YAML config (schema-validated) defining models and rules
  2. model-to-model (JSONata) and model-to-text (EJS) in the same rule model
  3. pipelines: rule outputs flow back into the model container and feed later rules
  4. declarative array fan-out: one rule → one file per array item, paths EJS-interpolated

The closest historical relative, mdgen (EJS on JSON models with fan-out), has been dead since 2016 and was bound to the StarUML model format.

Documentation

  • Getting started — first generator in 2 minutes
  • Guides — pipelines, construct & decision directives, regeneration/breakout, extending
  • Language reference — every construct, directive and option
  • Runnable examples — hello-world, api-scaffold (data model → types + SQL + forms), ci-workflows, regeneration

Getting started

Install

npm install sho-gen

Requires Node.js >= 22. (The npm package is named sho-gen; the CLI command it installs is shogen.)

CLI

shogen example.gen.json

Options:

| Option | Description | |---|---| | [config] / --config, -c | path to a generator file, or a directory (all .json/.yaml/.yml files in it are run) | | --output, -o | target directory for generated files (default: current directory) | | --print-result, -p | print the resulting model container as JSON |

API

import { run } from "sho-gen";

const generator = {
  models: [
    { name: "customers", file: "customers.json" },
    { name: "settings", json: { locale: "de" } },
  ],
  rules: [
    {
      input: "customers",            // array → one file per customer
      template: "Hello <%= name %>", // EJS
      output: { file: "gen/<%= id %>.txt" },
    },
    {
      input: "customers",
      template: { jsonata: "$.{ 'id': id }" }, // model-to-model
      output: { model: "ids" },                // stored back into the container
    },
  ],
};

const result = await run(generator, { workingDir: __dirname, outputDir: "out" });
console.log(result.ids);

Advanced usage (custom handlers, explicit instance):

import { ShoGen, registerTemplateHandler, validateGeneratorConfig } from "sho-gen";

registerTemplateHandler({
  name: "upper",
  async handle(model, rule) {
    /* return a result or null to pass to the next handler */
    return null;
  },
});

const shogen = new ShoGen({ workingDir: process.cwd(), outputDir: "gen" });
const config = validateGeneratorConfig(JSON.parse(raw)); // throws with readable errors
const models = await shogen.generate(config.models, config.rules);

Generator configuration

A generator is a JSON (or YAML) document with two parts, validated against schema/gen.schema.json:

Models

Every model is a named JSON value in the model container. Sources:

{
  "models": [
    { "name": "inline", "json": { "any": "value" } },
    { "name": "fromYaml", "yaml": "key: value" },
    { "name": "fromFile", "file": "model.json" },                          // format by extension
    { "name": "explicit", "file": { "path": "data.txt", "parse": "yaml" } } // format explicit
  ]
}

File paths are resolved relative to the generator file.

Rules

A rule reads its input from the container, optionally runs a template, and writes the result to an output:

{
  "rules": [
    {
      "input": "customers",                       // model name or JSONata path ("customers[0]")
      "template": ["# <%= name %>", ""],          // EJS: string or array of lines …
      "output": { "file": "docs/<%= id %>.md" }   // … file target, EJS-interpolated per item
    },
    {
      "input": "customers",
      "template": { "jsonata": "$count($)" },     // JSONata expression
      "output": { "model": "stats.count" }        // store into the container
    },
    {
      "input": "stats",
      "template": { "jsonata": "$" },
      "output": "gen/stats"                       // string output → JSON file (".json" appended)
    }
  ]
}

If input resolves to an array, the rule runs once per item — templates and output paths see the single item as their model.

Roadmap (not yet implemented)

These are planned but not part of the current release: Handlebars, prettier formatting of results, further model sources (CSV, JS/TS, MongoDB, markdown, PDF), partial injection into existing files, restricted regions, and an auto-loading plugin ecosystem (shogen-* packages).

Contributing & development

See CONTRIBUTING.md. Quick start:

npm install
npm run build   # tsc → dist/
npm test        # jest; examples are smoke-tested against frozen outputs
npm run lint    # type-check

The name: 諸元 (shogen) — "specifications, basic data": the model data the generators run on.