Parse arguments into a tree structure.
argstree is meant to be a minimal and less opinionated argument parser.
- Pure vanilla JavaScript. No external dependencies. ESM only.
- Preserves the order and structure of the provided arguments using a tree structure.
- Variadic arguments by default unless range options are specified.
- Includes a strict mode for unrecognized arguments.
- Can recognize and split combined aliases (e.g. from
-abcdto-a,-bc,-d). - Can recognize assigned values for options and commands (e.g.
--option=value,command=value). - Allows dynamic parsing of values, options, and commands.
- Double-dash (
--) is not treated as anything special but can be configured to be a non-strict subcommand.
- No other value data types other than strings.
- No automated help message generation.
- No asynchronous parsing.
- Expects shell commands as arguments (like
process.argv.slice(2)) and does not parse quoted strings (e.g.schema.parse(['--option="Hello World"'])). For parsing strings into command-line arguments, you can use packages like shell-quote and shlex. - Only parses and transforms arguments into a tree structure. It is still up to the consuming program to read and decide how to use the parsed arguments. This means more code to write and maintain just for arguments parsing and may not be worth the time and effort if you really only need a straightforward object of parsed options.
If you're looking to loop through arguments for more control, then argstree might be for you. Otherwise, you can check out more popular packages like commander, yargs, minimist, cac, and many more.
npm install argstreeFor the sake of brevity, not all features/properties are included in this document and it is advised to view the referenced types and code documentation to learn more. You can also check out the examples directory for more detailed usage.
The option() and command() functions both return a Schema object.
import command, { option } from 'argstree';Type: (arg: string, options?: Options) => this
Adds an option. The argument is overwritten if it already exists.
Type: (arg: string, options?: Options) => this
Adds a command. The argument is overwritten if it already exists.
Type: (args: readonly string[]) => Node
Parses arguments into a tree structure.
This returns the root Node which is a tree representation of the parsed arguments.
The example below introduces some of the available options, but the main idea here is that the structure of the resulting node would closely resemble the provided arguments depending on the schema configuration.
import command, { option } from 'argstree';
// `option()` can also create a schema,
// but `command()` is more suitable as the root schema
// creates a schema that expects at least 1 argument
const cmd = command({ min: 1, strict: true });
// adds an option '--input' that expects exactly 1 argument
cmd.option('--input', { min: 1, max: 1, alias: '-i' });
// adds a command 'run-script' that expects exactly 1 argument
// and will then take over the parsing of the rest of the arguments once matched
cmd.command('run-script', {
min: 1,
max: 1,
alias: ['run', 'rum', 'urn'],
init(run) {
// `run` is a schema for the 'run-script' subcommand
run.option('--if-present', { max: 0 });
run.command('--', { strict: false });
}
});
// parse arguments
const args = '--input src dist -i cli.js bin run --if-present build -- -h -i';
const root = cmd.parse(args.split(' '));
// traverse and log all nodes
const nodes = [root];
for (const node of nodes) {
const prefix = ' '.repeat(node.depth);
console.log('%s%s (%s):', prefix, node.id, node.type, node.args);
nodes.push(...node.children);
}null (command): [ 'dist', 'bin' ]
--input (option): [ 'src' ]
null (value): [ 'dist' ]
--input (option): [ 'cli.js' ]
null (value): [ 'bin' ]
run-script (command): [ 'build' ]
--if-present (option): []
run-script (value): [ 'build' ]
-- (command): [ '-h', '-i' ]
-- (value): [ '-h', '-i' ]
Option and command schemas differ only in some of the default values for the configuration options. At their core, they are the same and both can be configured similarly.
All options are optional.
Type: string | null
The option or command ID that is set to Node.id. If not provided, the default value is the Node.key.
Type: string | null
The option or command display name that is set to Node.name and is used for ParseError messages. If not provided, the default value is the Node.key.
Type: string | string[]
The initial arguments for the option or command. Strict mode does not apply to these values. Note that this is not a default value and additional arguments will be added on top of this list.
Type: number
The minimum number of arguments to read before the next parsed option or command.
A ParseError is thrown if the option or command does not satisfy this condition.
Type: number
The maximum number of arguments to read before the next parsed option or command. Arguments over the maximum limit are saved to the parent option or command instead. Assigned values are always treated as arguments for the option or command regardless of this option.
A ParseError is thrown if the option or command does not satisfy this condition or if the parent option or command cannot accept any more arguments.
Type: string | (string | string[])[]
The alias, list of aliases, or list of aliases with arguments for the option or command.
// root schema cannot have an alias
const cmd = command();
// single alias
cmd.option('--help', { alias: '-h' });
// multiple aliases
cmd.command('run-script', { alias: ['run', 'rum', 'urn'] });
// multiple aliases with additional arguments
cmd.option('--force', { alias: ['-f', ['--no-force', '0']] });
const root = cmd.parse(['-h', '-f', '--no-force', 'run', 'build']);
for (const node of [root].concat(root.children)) {
console.log(node.id, node.alias, node.args);
}null null []
--help -h []
--force -f []
--force --no-force [ '0' ]
run-script run [ 'build' ]
Aliases that start with a single dash (-) can be grouped together after a single dash (e.g. aliases -a, -b, and -c can be written as -abc).
If the option or command requires a value, it must be the last option when its alias is grouped together with other aliases, otherwise a ParseError is thrown.
const root = command()
.option('input', { alias: '-i' })
.option('--interactive', { alias: '-in' })
.option('--dry-run', { alias: '-n' })
.parse(['-nini', 'src', 'index.js']);
for (const node of root.children) {
console.log(node.id, node.alias, node.args);
}--dry-run -n []
--interactive -in []
input -i [ 'src', 'index.js' ]
Type: boolean
Default: true
When disabled, the option or command will not accept any arguments (except for assigned values) and are instead saved to the parent option or command if it can accept arguments. Otherwise, a ParseError is thrown and the argument is treated as an unrecognized argument.
Type: boolean
Default: true for option types and false for command types
Determines if the option or command can have an assigned value using the equal sign (e.g. --option=value, command=value). Otherwise, the option or command will not be matched.
// root schema cannot assigned values
const cmd = command();
// cannot read arguments but can assign a value
cmd.option('--input', { read: false, alias: '-i' });
// can read arguments but cannot assign a value
cmd.option('--output', { assign: false });
// cannot read arguments nor assign a value
cmd.option('--quiet', { read: false, assign: false, alias: '-q' });
const args = '-i=index.js src --output dist lib --quiet=0 -q=0 --quiet true';
const root = cmd.parse(args.split(' '));
for (const node of [root].concat(root.children)) {
console.log(node.id, node.type, node.args);
}null command [ 'src', 'true' ]
--input option [ 'index.js' ]
null value [ 'src' ]
--output option [ 'dist', 'lib', '--quiet=0', '-q=0' ]
--quiet option []
null value [ 'true' ]
Type: boolean
Default: false
When enabled, a ParseError is thrown for unrecognized arguments that look like an option (e.g. -o, --option). When enabled for a child node, unrecognized arguments are saved to the parent node instead. Can be one of the following values:
true- Enables strict mode for both self and descendants.false- Disables strict mode for both self and descendants.self- Enables strict mode for self but disable it for descendants.descendants- Disables strict mode for self but enable it for descendants.
Type: boolean
When true, parsed nodes will be treated as leaf nodes (no child nodes). When false, parsed nodes will be treated as parent nodes (has child nodes).
If not provided, this option defaults to true for option types or if there are no options or commands configured for the schema. Otherwise, this is false.
Type: (schema: Schema) => void
Called only once when the schema is created and is used to gain a reference to the schema object to add options and/or commands.
Type: (arg: Arg, node: Node) => XOR<Schema, Value> | XOR<Schema, Value>[] | boolean | void
Serves as a fallback for parsed arguments that cannot be recognized using the list of configured options and commands. Can have the following return values:
Schemas - Treated as options or commands. If the option or command (or for arrays, the last item) is a non-leaf node, then the next arguments will be parsed using that node.Values - Treated as value arguments and will be saved to either the current parent or child option or command depending on their provided options.false- The argument is ignored as if it was never parsed.- Empty array,
true,undefined- Fallback to the default behavior where the parsed argument may be treated either as a value or an unrecognized argument depending on the provided options.
import command, { isOption, option } from 'argstree';
const cmd = command({
strict: true,
parser(arg) {
// return an option when '--option' is matched
if (arg.key === '--option') {
return option({ args: arg.value });
}
// allow negative numbers in strict mode
if (isOption(arg.raw, 'short') && !isNaN(Number(arg.raw))) {
return { args: arg.raw, strict: false };
}
}
});
const root = cmd.parse(['--option=value', '-1']);
for (const node of root.children) {
console.log(node.id, node.type, node.args);
}--option option [ 'value', '-1' ]
Type: (node: Node) => void
Callback options are fired at specific events during parsing.
Type: (node: Node) => ParseOptions | void
Called when the node is created with its initial arguments. Some options for the node can be overridden by returning a ParseOptions object.
Called when the node receives an option or command child node.
Called after the node has received all arguments and direct child nodes that it can have.
Called once all nodes have been parsed and before any validation checks.
Called after throwing any validation errors for the node.
const logLevels = ['info', 'warn', 'error', 'debug'];
const cmd = command();
// immediately logs the help text and exit the process once parsed
cmd.option('--help', {
onCreate() {
console.log(`Usage: cmd --log-level <${logLevels.join(' | ')}>`);
process.exit();
}
});
// show version only after other nodes have been created (to prioritize '--help')
cmd.option('--version', {
read: false,
onBeforeValidate() {
console.log('v2.0.0');
process.exit();
}
});
// accepts exactly 1 argument that is then checked against
// a list of accepted values after the validation checks for the node
cmd.option('--log-level', {
min: 1,
max: 1,
onValidate(node) {
const value = node.args[0];
if (!logLevels.includes(value)) {
throw new Error(
`Option '${node.id}' argument '${value}' is invalid. Allowed choices are: ${logLevels.join(', ')}`
);
}
}
});
const argsList = [
'--log-level',
'--log-level log',
'--log-level log --version --help'
];
for (const args of argsList) {
try {
cmd.parse(args.split(' '));
} catch (error) {
console.error(String(error));
}
}ParseError: Option '--log-level' expected 1 argument, but got 0.
Error: Option '--log-level' argument 'log' is invalid. Allowed choices are: info, warn, error, debug
Usage: cmd --log-level <info | warn | error | debug>
Nodes can have additional metadata that can be set to Node.meta.
interface Metadata {
key: string;
}
const cmd = command<Metadata>({
parser(arg) {
// format: --{id}:{key}={value}
const match = arg.key.match(/^--(.+?):(.+)$/);
if (!match) return;
const [, id, key] = match;
return option({
id,
args: arg.value,
onCreate(node) {
node.meta = { key };
}
});
}
});
const root = cmd.parse([
'--file:package.json',
'--ext:.js=.mjs',
'--map:@scope/[name]=dist/[name]'
]);
for (const node of root.children) {
console.log(node.id, node.meta?.key, node.args);
}file package.json []
ext .js [ '.mjs' ]
map @scope/[name] [ 'dist/[name]' ]
A ParseError is thrown when an error occurs during parsing.
import command, { ParseError } from 'argstree';
const cmd = command({ min: 1, max: 1, strict: true })
.option('--input', { min: 1, max: 1, alias: '-i' })
.option('--force', { name: 'FORCE', max: 0, alias: '-f' })
.command('start', { read: false });
const argsList = [
'--output',
'-efghi index.js',
'--input index.js',
'lib -i -f=0',
'-i index.js lib -f=0',
'lib start index.js'
];
for (const args of argsList) {
try {
cmd.parse(args.split(' '));
} catch (error) {
if (error instanceof ParseError) {
console.error(error.code, error.node.id, error.message);
}
}
}UNRECOGNIZED_ARGUMENT null Unrecognized argument: --output
UNRECOGNIZED_ALIAS null Unrecognized aliases: -(e)f(gh)i
RANGE null Expected 1 argument, but got 0.
RANGE --input Option '--input' expected 1 argument, but got 0.
RANGE --force Option 'FORCE' expected 0 arguments, but got 1.
UNRECOGNIZED_ARGUMENT start Command 'start' does not recognize the argument: index.js
This package includes some utility functions that can be useful during and after parsing.
Type: (node: Node) => Node[]
Flattens the node tree structure into an array of nodes.
Type: (arg: string, type?: 'long' | 'short') => boolean
Determines if the argument looks like an option. By default, both long (e.g. --option) and short (e.g. -a, -abc) options are valid unless the specific type of option is provided.
Type: (value: string, matches: string[]) => Split
Splits the string based on the provided matches in order.
import { split } from 'argstree';
console.log(split('foobarbaz', ['ba', 'foo']));{
items: [
{ value: 'foo', remainder: false },
{ value: 'ba', remainder: false },
{ value: 'r', remainder: true },
{ value: 'ba', remainder: false },
{ value: 'z', remainder: true }
],
values: [ 'foo', 'ba', 'ba' ],
remainders: [ 'r', 'z' ]
}
Licensed under the MIT License.