Skip to content

Program ​

jinju() builds a program from a tree of commands. The program's methods then run it or render its help.

Keep defining the program separate from running it. The definition is plain configuration, and the program can be run as many times as you like, with any input.

ts
import { 
command
,
jinju
,
packageVersion
} from 'jinju';
const
init
=
command
({
description
: 'Create a project',
run
: () => {} });
const
build
=
command
({
description
: 'Build the project',
run
: () => {} });
const
watch
=
command
({
description
: 'Rebuild on changes',
run
: () => {} });
const
app
=
jinju
({
name
: 'app',
version
:
packageVersion
(import.meta.
url
),
description
: 'My app',
commands
: {
init
,
build
: {
$
:
build
,
watch
}, // app build, app build watch
}, }); await
app
.
run
();

Config ​

jinju({ name?, version?, description?, commands, strict?, help?, error?, env?, stdout?, stderr?, exit? })

OptionDefaultDescription
commandsThe command tree. Required.
nameThe script's file nameShown in usage and error hints.
versionPrinted by --version. Without it, there's no --version flag.
descriptionShown at the top of the root help page.
stricttrueReject unknown flags. With false, they're ignored.
helpnew HelpTemplate()Renders help pages. See Help.
errornew ErrorTemplate()Renders errors. See Errors.
envprocess.envWhere flags' env variables are read from.
stdoutprocess.stdout.writeReceives help and version text.
stderrprocess.stderr.writeReceives error output.
exitSets process.exitCodeReceives the exit code.

See Testing for how env, stdout, stderr and exit help in tests.

The command tree ​

  • Each key is a command name. Its value is a command, or an object of subcommands.
  • $ is the command for that level itself. In { build: { $: build, watch } }, app build runs build and app build watch runs watch.
  • A level without $ is a group: running it shows its help, which lists its subcommands.
  • A :name key is a dynamic segment, which reads a value before the command name.
  • { $: command } at the root makes a single-command CLI.

Mistakes such as duplicate names, $ holding a subtree or invalid names throw a ConfigError when jinju() is called. Mistakes in dynamic segments are also compile-time errors.

The program ​

jinju() returns a Program:

MemberDescription
run(argv?)Parses argv, routes it to a command and runs it. Resolves to the exit code.
help(path?)Returns the help text for a command path, without printing it.
nameThe program's name: name from the config, or the script's file name.

Running ​

run() reads process.argv.slice(2) by default:

ts
await 
app
.
run
(); // node app.js build --watch → ['build', '--watch']

Pass argv yourself to run the program with other input, such as in tests, when embedding a CLI in another tool, or when your arguments come from somewhere other than process.argv:

ts
const 
code
= await
app
.
run
(['build', '--help']);
const code: number

run() resolves to the exit code and also passes it to exit. It rejects only when a run handler throws an error that isn't a JinjuError.

Rendering help ​

help(path?) returns the help text for a command path, e.g. app.help(['build', 'watch']), or the root page with no path. This is useful to generate docs or snapshot help in tests. It throws an UnknownCommandError for an unknown name.

Routing ​

run() takes the longest command path that matches argv. Static names win over dynamic segments.

Flags can come before the command name when a command further down the tree inherits them from a scope: app -v build. A flag that no command below that point inherits, or --, ends the command path.

Built-in flags ​

  • --help/-h prints the help page for the matched level, and exits with 0.
  • --version prints version, and exits with 0. It only exists when version is set.

Running a group (a level without $) prints its help. It exits with 0 when there was no input at all (just app), and with 1 otherwise (e.g. app build when build is a group). An unknown command name there is an error.

Exit codes ​

app.run() resolves to the exit code and passes it to exit, which by default sets process.exitCode, so Node exits with it once pending work is done.

CodeWhen
0Success, --help or --version
1Invalid input, or a JinjuError thrown from run
any numberReturned from run

Reading the version ​

packageVersion(from) reads version from the nearest package.json at or above from, usually your CLI's import.meta.url. It throws a ConfigError if there's no package.json or it has no version.