@codejen/jen

cli.

One command, a handful of options, and a generator's own params as --flags.

jen never asks. Whatever it doesn't understand, it says so and quits before writing anything.

Running a generator

Options

jen [generator] [--param=value …] [options]. The generator is a name (class), with its pack (cpp:class) or with its source (user:class).

  • --dry-run, -n: only show the plan, write nothing.
  • --force, -f: overwrite existing files instead of skipping them.
  • --dir=<path>: where the files go, instead of the current directory. See where files go.
  • --from <package>: fetch a pack via npm, run it once, then remove it – or a package a pack runs, e.g. a Yeoman generator: jen --from generator-code yeoman:code.
  • --list, -l: list every generator and where it comes from.
  • --json: with --list, the same list as JSON.
  • --help, -h: jen's own help, or with a generator, its params.

Params

Everything else on the command line is a param of the generator. A value always takes the = form: --name=Foo. A boolean param works as a bare flag (--moveOnly), or with =true/=false – yes/no, on/off and 1/0 work too. Anything else fails, rather than being read as false.

A param without a default is required. Leave it out and jen stops with Missing: --name before anything is written – it never prompts for it.

Some params only take a few values, like --std=20 or --std=23 for cpp:enum. Anything else stops jen with the values it does take: cpp:enum --std: expected one of "20", "23", got "17".

list, dry-run, force, from and help are jen's own, so a generator can't use them as param names. json and dir can be: a generator with its own dir param gets --dir for itself.

Unknown flags

A flag the generator has no param for stops jen too. A typo like --moveonly or --namepsace=gfx would otherwise be dropped without a word, and you'd get different code than you asked for. jen names every unknown flag at once, suggests the closest param for a near-miss, and lists the ones the generator knows:

$ jen cpp:class --name=Foo --copyable --moveonly
cpp:class (pack @codejen/pack-cpp) – create a C++ class (header + source), wired into the CMakeLists.txt with # jen:sources
cpp:class has no --copyable, --moveonly (did you mean --moveOnly?). Known: --name, --namespace, --moveOnly, --resource, --nullValue, --destroy, --withTest. (jen cpp:class --help)

--dir is always accepted, whether the generator has its own dir param or not.

Reading the plan

Every run prints its plan: one line per file, and what happens to it.

  • +: the file is created.
  • ~: the file is changed, with the line that goes in.
  • !: the file is overwritten, with --force.
  • -: the file is deleted.
  • =: nothing to do – the file exists, the line is already there, or nothing matched.
  • ?: a problem, so the action is skipped – a missing file or marker, or a path outside the project.

When the project root isn't where you stand, the plan says where jen found it and why, e.g. root: .. (package.json). With --dry-run, the plan is all there is.

Fetching packs

--from <package> (or <package>@<version>) installs a pack into a throwaway directory, runs it once and removes it again – no package.json entry, no node_modules left in your project. Any other package is installed the same way for a pack to run, e.g. a Yeoman generator: jen --from generator-code yeoman:code. The generator name stays the same as for an installed pack: --from only changes where jen gets it.

Ask for a prefixed generator whose pack isn't installed, like lua:function, and jen fetches @codejen/pack-lua the same way. That only ever pulls from jen's own @codejen scope, so a typo can't install someone else's package; for any other scope, or an unprefixed name, jen prints a hint to use --from instead.

Packs fetch, too – only names fixed in their own code, never one from the command line: pack-yeoman its fixed list of generators, pack-js npm create vite@latest and the latest versions for js:esbuild. jen uses your own npm, with --ignore-scripts for its installs. JEN_NO_FETCH=1 turns all of it off.

Looking around

--help

jen <generator> --help shows one generator and its params the way you'd type them: required ones marked (required), strings with their default (--namespace=""), booleans as a bare flag, each followed by its description. A param that only takes a few values lists them all, the default first: --std=23|20.

Below the params come the generator's examples, ready to copy – the first one also the way to run it without installing jen, through npx --yes @codejen/jen. Last, a link to the pack's documentation, if it has one.

$ jen cpp:enum --help
cpp:enum – create an enum class with to_string() and a std::formatter specialization (header-only)
  --name (required)  the enum, any case: game-state and GameState both make GameState in GameState.h
  --values (required)  comma-separated enumerators, e.g. "idle, running, paused"
  --namespace=""  C++ namespace to wrap it in, nested ones as engine::physics; empty means the global namespace
  --std=23|20  C++ standard: 23, or 20 (std::abort instead of std::unreachable)
  --withTest  add a doctest case (needs cpp:doctest)

examples:
  jen cpp:enum --name=GameState --values="menu, playing, paused"
  jen cpp:enum --name=GameState --values="menu, playing, paused" --std=20 --withTest
  npx --yes @codejen/jen cpp:enum --name=GameState --values="menu, playing, paused"  (without installing jen)

docs: https://learosema.github.io/jen/recipes.html

--list

jen --list shows every generator, where it comes from, and which ones are shadowed by another of the same name further up the search path. Without a generator name at all, jen prints the same list.

--list --json

jen --list --json prints the same catalog as JSON on stdout, and nothing else, for scripts, editors and other tools. Each generator has:

  • name: the name to run it by, with the pack prefix, e.g. cpp:class.
  • source: where it comes from, e.g. pack @codejen/pack-cpp, project or built-in.
  • pack: for a generator from a pack, the pack's name, and its version, description and homepage where its package.json has them.
  • shadowed: true if another generator of the same name is found first, so this one only runs with its source prefix.
  • description: what it makes, or "".
  • params: each param by name, in the order --help shows them, with
    • type: string or boolean, following from its default,
    • required: true if it has no default,
    • default, choices (every value it takes), path and description, only where the generator gives them.
  • examples: example calls, each as jen, with jen installed, and as npx, without: npx --yes @codejen/jen …, with --from for a pack outside @codejen. [] if the generator has none.

A generator that fails to load gets an error with the message instead of description, params and examples, and the listing goes on with the next one.

$ jen --list --json
[
  {
    "name": "cpp:enum",
    "source": "pack @codejen/pack-cpp",
    "pack": {
      "name": "@codejen/pack-cpp",
      "version": "1.3.0",
      "description": "jen pack for C++: class/shader scaffolding in src/, plus SDL3 (+ OpenGL) app starters",
      "homepage": "https://learosema.github.io/jen/recipes.html"
    },
    "shadowed": false,
    "description": "create an enum class with to_string() and a std::formatter specialization (header-only)",
    "params": {
      "name": {
        "type": "string",
        "required": true,
        "description": "the enum, any case: game-state and GameState both make GameState in GameState.h"
      },
      "std": {
        "type": "string",
        "required": false,
        "default": "23",
        "choices": ["20", "23"],
        "description": "C++ standard: 23, or 20 (std::abort instead of std::unreachable)"
      },
      …
    },
    "examples": [
      {
        "jen": "jen cpp:enum --name=GameState --values=\"menu, playing, paused\"",
        "npx": "npx --yes @codejen/jen cpp:enum --name=GameState --values=\"menu, playing, paused\""
      },
      …
    ]
  },
  …
]

For scripts

Exit codes

jen exits with 0 when it ran: files written, nothing to do, or only a plan with --dry-run. Anything else exits with 1 and a message on stderr: a missing or unknown param, a boolean it can't read, a generator that isn't found – and jen without a generator name, after listing them. Nothing is written in any of these cases.

Passing flags on

Some generators run something else and pass the command line on, like pack-yeoman, which answers a Yeoman generator's questions from its flags. Such a generator sets passthrough: true: jen then accepts flags it has no param for and hands them over, with the positionals after the generator name, in ctx.args.