Skip to content

Flags ​

Flags are named options: --port 8080, -v, --dry-run. You create them with the flag.* creators and add them to a command (or scope) under flags. The object key becomes the flag's name, converted from camelCase to kebab-case.

ts
import { 
command
,
flag
} from 'jinju';
command
({
description
: 'Serve files',
flags
: {
port
:
flag
.
number
({
alias
: 'p',
description
: 'Port to listen on',
integer
: true }),
dryRun
:
flag
.
boolean
({
description
: "Don't write anything" }), // --dry-run
},
run
: ({
flags
}) => {},
flags: {
    port: number | undefined;
    dryRun: boolean;
}
});

Value flags ​

These take a value, written as --name value, --name=value, -n value or -nvalue:

CreatorTypeOptionsNotes
flag.string(options)stringchoices?With choices, the type is the union of them
flag.number(options)numbermin?, max?, integer?Rejects non-numbers, and values outside the bounds
flag.url(options)URLParsed with new URL()
flag.email(options)stringChecks the value looks like an email address
flag.directory(options)stringexists?Resolved to an absolute path
flag.file(options)stringexists?Resolved to an absolute path
flag.custom(parse, name?)Your parser'sYour parser's optionsSee custom parsers
flag.schema(schema, name?)The schema outputSee Standard Schema

choices narrows the type:

ts
import { 
flag
, type
InferFlagType
} from 'jinju';
const
mode
=
flag
.
string
({
description
: 'Mode',
choices
: ['fast', 'safe'] });
type
Mode
=
InferFlagType
<typeof
mode
>;
type Mode = "fast" | "safe" | undefined

With exists: true, flag.directory and flag.file also check that the path exists and is the right kind of entry.

Switches ​

flag.boolean and flag.count are switches. They never take the next argument as a value.

CreatorDefaultBehaviour
flag.boolean(options)false--x → true, --no-x → false. The last occurrence wins.
flag.count(options)0Counts occurrences: -vvv → 3. --no-x resets the count to 0.

A switch can also be set explicitly with --x=<value>, using true/false, 1/0, yes/no or on/off. --no-x=<value> inverts the value. The built-in --help and --version follow the same rules, so --help=false doesn't show help.

A switch always has a value, so its type is never undefined. To default a boolean to true, use .default(true). Help then shows it as --[no-]name.

Shared options ​

Every flag creator takes these options:

OptionDescription
descriptionShown in help. Required.
aliasAnother name, without dashes. One character gives -x, longer names give --xyz.
hiddenLeave the flag out of help. It's still accepted.
envAn environment variable to read when the flag isn't given. See Environment variables.

Modifiers ​

Flags are immutable: each modifier returns a new flag, so one definition can be reused with different settings.

ModifierEffect
.required(on = true)Must be provided. Clears any default.
.multiple(on = true)Repeatable, collected into an array. Converts any existing default (v ↔ [v]).
.default(value)Used when absent. Clears required. Must be a non-empty array when multiple.

Switches only have .default(value).

The modifiers decide the type run receives:

DefinitionType
flag.url(o)URL | undefined
flag.url(o).multiple()URL[] | undefined
flag.url(o).default(url)URL
flag.url(o).required().multiple()URL[]
flag.string({choices: ['a', 'b'], …})'a' | 'b' | undefined
ts
import { 
command
,
flag
} from 'jinju';
const
tag
=
flag
.
string
({
alias
: 't',
description
: 'Tag to apply' });
command
({
description
: 'Publish',
flags
: {
tag
:
tag
.
multiple
().
default
(['latest']),
registry
:
flag
.
url
({
description
: 'Registry' }).
required
(),
},
run
: ({
flags
}) => {},
flags: {
    tag: string[];
    registry: URL;
}
});

A .multiple() flag is repeated on the command line (-t a -t b). Giving a flag that isn't .multiple() more than once is an error.

Custom parsers ​

flag.custom(parse, name?) turns a parser into a flag creator. The parser receives the raw string and the options passed to the creator, and returns the value. Throw to reject the input: the error message is shown to the user. Parsers may be async.

ts
import { 
command
,
flag
} from 'jinju';
const
duration
=
flag
.
custom
((
input
: string, {
max
}: {
max
?: number }) => {
const
match
= /^(\d+)(ms|s|m)$/.
exec
(
input
);
if (!
match
) throw new
Error
('must be a duration like 500ms, 10s or 5m');
const
ms
=
Number
(
match
[1]) * {
ms
: 1,
s
: 1000,
m
: 60_000 }[
match
[2] as 'ms' | 's' | 'm'];
if (
max
!==
undefined
&&
ms
>
max
) throw new
Error
(`must be at most ${
max
}ms`);
return
ms
;
}, 'duration');
command
({
description
: 'Wait',
flags
: {
timeout
:
duration
({
description
: 'Timeout',
max
: 60_000 }).
default
(5000) },
run
: ({
flags
}) => {},
flags: {
    timeout: number;
}
});

The second argument names the value in help: --timeout <duration>. arg.custom accepts the same parsers.

Command-line syntax ​

  • --name value, --name=value, -n value, -nvalue and -n=value all set a value.
  • Short switches can be bundled: -abc is -a -b -c, and -vvv counts three times. A bundle can end with a value flag: -vp 8080.
  • Negative numbers are values, not flags: --offset -5.
  • -- ends flag parsing. Everything after it is a positional value, even if it starts with -.
  • A value flag at the end of the input, or followed by another flag, is a "requires a value" error.

Errors ​

Invalid input is reported with the flag's label and the parser's message, and every problem is reported at once:

sh
$ app sync . --mode slow --times 0.5
error: Invalid value 'slow' for --mode: must be one of fast, safe
error: Invalid value '0.5' for --times: must be an integer

Run 'app sync --help' for usage.

Unknown flags are errors unless the program sets strict: false. See Errors to change how errors look.