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.
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? })
| Option | Default | Description |
|---|---|---|
commands | The command tree. Required. | |
name | The script's file name | Shown in usage and error hints. |
version | Printed by --version. Without it, there's no --version flag. | |
description | Shown at the top of the root help page. | |
strict | true | Reject unknown flags. With false, they're ignored. |
help | new HelpTemplate() | Renders help pages. See Help. |
error | new ErrorTemplate() | Renders errors. See Errors. |
env | process.env | Where flags' env variables are read from. |
stdout | process.stdout.write | Receives help and version text. |
stderr | process.stderr.write | Receives error output. |
exit | Sets process.exitCode | Receives 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 buildrunsbuildandapp build watchrunswatch.- A level without
$is a group: running it shows its help, which lists its subcommands. - A
:namekey 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:
| Member | Description |
|---|---|
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. |
name | The program's name: name from the config, or the script's file name. |
Running
run() reads process.argv.slice(2) by default:
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:
const code = await app.run(['build', '--help']);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/-hprints the help page for the matched level, and exits with0.--versionprintsversion, and exits with0. It only exists whenversionis 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.
| Code | When |
|---|---|
0 | Success, --help or --version |
1 | Invalid input, or a JinjuError thrown from run |
| any number | Returned 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.