pargs
v1.7.0
Published
A useful wrapper for `util.parseArgs`
Maintainers
Readme
pargs 
A wrapper for node’s built-in util.parseArgs with helpful features added.
Usage
#!/usr/bin/env node
import pargs from 'pargs';
const {
help,
positionals,
values,
errors, // a mutable string array; push to it and pargs will include your error messages.
name, // if subcommands are used
tokens,
} = await pargs(import.meta.filename, options);
// do extra validation here
await help(); // to handle `--help` and print the help text if needed, or to print errors and exitHelp
Help text is automatically read from a help.txt file adjacent to import.meta.filename, when one is present.
When no help.txt file exists, help text is generated automatically from the provided config - the usage line, options (with their types, choices, defaults, and short flags), subcommands, and positional argument requirements. A help.txt file, when present, always takes precedence over the generated text.
The program name in the generated usage line comes from the nearest package.json (the bin entry that points at the entrypoint, falling back to the unscoped package name, then to the file’s basename).
The following config fields exist solely to enrich generated help; they are ignored by util.parseArgs:
options[name].description: a string describing the option.options[name].placeholder: the value placeholder shown for a non-boolean option (eg,placeholder: 'MM/DD/YYYY'renders--before <MM/DD/YYYY>instead of<string>).options[name].group: a heading to group the option under; ungrouped options appear first underOptions, and--helpis always listed there.options[name].defaultDescription: a string shown as the default in place of the actualdefaultvalue - useful to mask a secret, or to show an env-derived default symbolically (eg,$HOME/.cacherather than the resolved path).positionals: an array of{ name, description?, rest? }, used to name positionals in the usage line (<name>when required perminPositionals, else[name];rest: truemakes it variadic) and to render anArguments:section.description(on a config or subcommand): either a string (used as the summary), or an object{ summary?, examples?, sections? }, whereexamplesis an array of strings or{ command, description? }, andsectionsis an array of{ title, body }for free-form blocks (eg,Behavior,Exit codes).
Option defaults are shown as (default: …), except that a boolean option’s default: false is omitted (it is the implicit default; default: true is still shown). Array defaults render as [a, b] / [].
await an invocation of the help function returned from the pargs call to handle --help and print the help text if needed, or to print errors and exit.
It returns which path it handled - 'version', 'help', 'errors', or false when there was nothing to handle.
Passing { exit: false } makes it return instead of calling process.exit(), which is what a function that parses args but is not itself the process entrypoint needs, since process.exit can not be caught.
process.exitCode is still set, so if (await help({ exit: false })) { return; } is a working drop-in.
Version
--version is provided automatically: the same await help() call handles it, printing the version field from the nearest package.json, prefixed with v, and exiting.
The root version config controls it.
It is deprecated, and marked @deprecated in the types:
it exists so a CLI moving onto pargs can keep the exact --version output it already ships, and new code should take the default.
true,undefined, or absent: thepackage.jsonlookup described above.- a string: printed verbatim, with no
vprefix and nopackage.jsonlookup - useful to match an existing CLI's output, or to print more than the bare number. false: no built-in--versionat all. It is then an unknown option, and the generated help omits its row.
Subcommands inherit the root version: a CLI that has no built-in --version, or that prints its own string, means that at every level.
A subcommand may declare its own version to override what it inherited.
Unlike help, version is not reserved - if you define your own version option, yours is used instead and the built-in one is not added.
Note that this means you must print it yourself; await help() will not, and a --version it does not own falls through to your program's normal path - including when a defaultCommand is what parsed the flag.
Declaring an options.version alongside a root version of false or a string throws, since the two disagree about who owns --version;
a root version of true is the default, so it is accepted and your option wins.
That applies to one level: a subcommand declaring its own version option alongside a version it merely inherited yields to the option instead of throwing, since the two were not written in one place.
Options
See the node.js parseArgs documentation for some context.
In TypeScript, options is a union discriminated on type, so a table hoisted into its own variable needs as const (or 'string' as const per entry) - otherwise type widens to string and matches no branch. Written inline in the call, it is inferred correctly as-is.
strict: can not be set tofalse- strictness all the way.allowNegative: can not be set tofalse.usageOnError: where the usage text goes whenhelp()reports an error the user did not ask for.'stdout'is the default and current behavior;'stderr'keeps it off stdout, which matters for a CLI whose own output is piped or redirected;falseprints no usage at all, matching what most parsers do on a bad flag. Error messages always go to stderr regardless. A--helpthe user explicitly asked for is program output, so it always prints, and always to stdout, even alongside an error and even underfalse. Inherited by subcommands.shorts: deprecated, and marked@deprecatedin the types - it exists so a CLI moving onto pargs can keep the-h/-Vshort flags it already ships, and new code should use the long forms, which are always available. Opts in to short flags for the reserved--help/--version.truerequests the conventional-hand-V, and silently yields either letter to an option that already claims it (so-vcan stay yours while-Vis the built-in version). An object -{ help?, version? }- requests specific letters, and throws if one collides, since you asked for it by name. Omitted, orfalse, registers nothing, which is the default.shorts.versionis not allowed when there is no built-in--versionto attach it to. Subcommands inherit the rootshortsand may declare their own to override it; an inherited letter that collides with a subcommand's own option is skipped rather than throwing, since the collision was not written in one place.negation:'exclusive'(the default) reports an error when both--xand--no-xappear in the same invocation;'last-wins'suppresses that error and leaves the parsed value alone, so the last occurrence wins for a scalar boolean, and every occurrence is collected for amultipleone. May be set at the root, or per-option (options[name].negation) to override the root; it does not inherit into subcommands. The reserved--helpand--versionare unaffected ---no-helpis always an unknown option.args: when omitted, pargs usesprocess.argv, with the node binary and the entrypoint filtered out. When provided, it is the argument list, used verbatim - nothing is filtered out of it, each element is coerced withString, and a non-array throws. An explicitargsalso governs subcommand anddefaultCommandrouting, and suppresses theprocess.argvmutation that subcommand routing otherwise performs. A subcommand's ownargs, if it declares one, is overridden by the parent's routing.options.type: in addition to'boolean'and'string':'enum': when provided, achoicesstring array is also required. The value is validated only when one is present - an option that was not passed and has nodefaultis not an error. Withmultiple, each element is validated individually.'number': validates the value is a finite number and coerces it from a string.'integer': validates the value is a finite integer and coerces it from a string.
options[name].variadic: deprecated, and marked@deprecatedin the types - it exists so a CLI moving onto pargs can keep an argv shapeutil.parseArgsdeliberately does not support, and new code should takemultiplewith a repeated option (--plugins a --plugins b), which is unambiguous about where the list ends. One occurrence collects every following value-like argument, so--plugins a b cyields all three rather than droppingbandcinto positionals. The run stops at the first argument that looks like an option (unless the option is alsogreedy, which widens what counts as a value for the whole run), at--, or at the end of the list; an attached value (--plugins=a) does not start a run. A lone-is a value, not an option, so it is collected like any other ---plugins a - byields all three. Impliesmultiple: true, so repeated occurrences accumulate and the value is an array; an explicitmultiple: falsethrows, and a scalardefaultis wrapped in an array for you. The one exception isoptionalValue: true, where an occurrence with no values at all yields the scalartruerather than an empty array, exactly as it does withoutvariadic. Not allowed on abooleanoption.options[name].optionalValue: the option's value is optional, so it may be passed bare.truemakes a bare occurrence yield the booleantrue, widening the value type tostring | true; a string makes it yield that string, which is injected as an ordinary value and so leaves the type alone. An explicit value is still taken in every spelling --p x,-px,--package=x, and at the end of a short cluster (-up x) - and an option-looking next token is not consumed, so-p --stdoutis a bare-pfollowed by--stdout. Withmultiple, a bare occurrence undertruecontributes nothing to the list, unless every occurrence was bare, in which case the value is the scalartrue; under the string form it contributes that string like any other value. Undertrue, a bare occurrence is exempt fromenumchoices and fromnumber/integercoercion, sincetrueis not one of those; under the string form the injected value is validated like any other, so anoptionalValueoutside an option'schoicesis an error naming that option. AnoptionalValueof''is a value like any other, not "no optional value". A declareddefaultis left alone - an unpassed option keeps its default rather than collapsing totrue. Not allowed on abooleanoption, which already has this shape.options[name].greedy: deprecated, and marked@deprecatedin the types - it exists so a CLI moving onto pargs can keep accepting--opt --value, whichutil.parseArgsrejects as ambiguous while naming the unambiguous spelling in the error (--opt=--value); that is what new code should use. The option takes the next argument as its value even when that argument looks like an option, so--append-git-log --first-parentworks without the=form.--is never consumed ---x --keeps its argument is ambiguous error, and--x=--is the way to pass a literal--. Note that a greedy option swallows whatever follows it, including--helpand--version; that is inherent to the arity, and matches how other parsers treat it. Not allowed on abooleanoption. When any option in a config asks for an arityutil.parseArgscan not express, the argument list is rewritten before parsing, sotokensdescribes the rewritten list: a fused value is reported as one token withinlineValue: true. Configs that do not opt in get back the identical array, and therefore identicaltokens. A bareoptionalValueoccurrence is reported the wayparseArgsreports any valueless option, withvalueandinlineValueundefined. One constraint comes with the rewriting: at a level whose own options opt in, no argument may contain a NUL byte, and one that does throws. The check is per parsed level, not per config, so a subcommand that opts into nothing does not impose it on its own arguments.process.argvcan never contain a NUL byte, so this only ever concerns a programmaticargs.partialValues: whentrue, a fatal parse error (an unknown option, a missing option argument) returns whatever else parsed cleanly instead of an emptyvalues. Only declared options survive, and only when the loosely-parsed value still matches the declared type;enumchoices andnumber/integercoercion are applied as usual, and anything that fails is dropped.errorsstill holds only the single fatal error,positionalscome from the loose reparse and so do not re-apply the configured positional policy, and an option with adefaultmay be missing - sovaluesis a partial of its usual type. Defaults tofalse, and does not inherit into subcommands.allowPositionals: in addition to a boolean, or an integer representing the maximum number of allowed positional arguments.minPositionals: an integer representing the minimum required number of positional arguments.subcommands: if provided, must be an object. Keys are the subcommand names (eg, innpm ls,lsis the subcommand), and values are the configuration options for each subcommand - as if they were a top-level invocation. The subcommand's generated usage line names the command it documents (eg,Usage: cli build [options]). Rootoptionsalongsidesubcommandsare parsed git-style: only when they lead the argument list, before the subcommand name (eg,cli --dry-run build). The first argument that is not one of them selects the subcommand, and everything from there on is the subcommand's to parse - so a root option given after the subcommand name is an unknown option for that subcommand. When at least one root option is passed and no subcommand follows (eg,cli --setup), that is the whole invocation: there is no error, andcommandisundefined; a bare invocation with no arguments at all is still anunknown commanderror. A root option takes its values exactly as it would with no subcommands, except that avariadicrun of values ends at a subcommand name (socli --files a b buildrunsbuild). The first value is taken as it would be anywhere, socli --files buildcollectsbuild; a later value spelled like a subcommand can still be given inline, as--files=build. An error in a root option (eg, an invalidenumchoice) is reported by the returnedhelp(), along with any of the subcommand's, even when a subcommand runs. A root-level--helpor--versionis answered at the root, even when a subcommand follows it (seedefaultCommandfor how one is answered when there is a default command); when no subcommand follows it,commandisundefinedwith no error, even though the types describe it as present, so callhelp()before readingcommand.defaultCommand: only allowed alongsidesubcommands; must be the name of one of them. When the first argument (after any leading root options) is not a recognized subcommand (including when it is a flag, or absent entirely), the rest of the argument list is parsed against this command instead of erroring withunknown command. A root option that the default command also accepts - by name, or by a short flag it claims, including the ones its own--help/--versionresolve to - belongs to the default command, so a root that mirrors its default command's options (eg, for the help text) keeps routing them there; when the root also has options of its own, the generated help lists the shared ones (other than any with agroupof their own) under a<command> optionsheading, with a usage line of their own, and shows the short flags the default command actually uses. A leading--help/--versiondoes not end the root options, but it is passed along to the default command - even when a subcommand name follows it - which answers it with the root help, as before; a--versionthe default command does not have (eg,version: false) is passed along too, and is an unknown option there, as before. This enables a bare default form (eg,vers <input>) to coexist with named subcommands.--help/--versionstill apply at the level invoked: a root-level--help(no recognized subcommand) shows the root help (the command list), not the default command's help.
Install
``
npm install --save pargsLicense
MIT
Thanks
Thanks to @ibakaidov for donating the pargs package name!

