Page Menu
Home
c4science
Search
Configure Global Search
Log In
Files
F95052292
command.js
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Subscribers
None
File Metadata
Details
File Info
Storage
Attached
Created
Thu, Dec 12, 11:46
Size
60 KB
Mime Type
text/x-c++
Expires
Sat, Dec 14, 11:46 (1 d, 23 h)
Engine
blob
Format
Raw Data
Handle
22921113
Attached To
rOACCT Open Access Compliance Check Tool (OACCT)
command.js
View Options
const
EventEmitter
=
require
(
'events'
).
EventEmitter
;
const
childProcess
=
require
(
'child_process'
);
const
path
=
require
(
'path'
);
const
fs
=
require
(
'fs'
);
const
{
Argument
,
humanReadableArgName
}
=
require
(
'./argument.js'
);
const
{
CommanderError
}
=
require
(
'./error.js'
);
const
{
Help
}
=
require
(
'./help.js'
);
const
{
Option
,
splitOptionFlags
}
=
require
(
'./option.js'
);
const
{
suggestSimilar
}
=
require
(
'./suggestSimilar'
);
// @ts-check
class
Command
extends
EventEmitter
{
/**
* Initialize a new `Command`.
*
* @param {string} [name]
*/
constructor
(
name
)
{
super
();
/** @type {Command[]} */
this
.
commands
=
[];
/** @type {Option[]} */
this
.
options
=
[];
this
.
parent
=
null
;
this
.
_allowUnknownOption
=
false
;
this
.
_allowExcessArguments
=
true
;
/** @type {Argument[]} */
this
.
_args
=
[];
/** @type {string[]} */
this
.
args
=
[];
// cli args with options removed
this
.
rawArgs
=
[];
this
.
processedArgs
=
[];
// like .args but after custom processing and collecting variadic
this
.
_scriptPath
=
null
;
this
.
_name
=
name
||
''
;
this
.
_optionValues
=
{};
this
.
_optionValueSources
=
{};
// default < config < env < cli
this
.
_storeOptionsAsProperties
=
false
;
this
.
_actionHandler
=
null
;
this
.
_executableHandler
=
false
;
this
.
_executableFile
=
null
;
// custom name for executable
this
.
_defaultCommandName
=
null
;
this
.
_exitCallback
=
null
;
this
.
_aliases
=
[];
this
.
_combineFlagAndOptionalValue
=
true
;
this
.
_description
=
''
;
this
.
_argsDescription
=
undefined
;
// legacy
this
.
_enablePositionalOptions
=
false
;
this
.
_passThroughOptions
=
false
;
this
.
_lifeCycleHooks
=
{};
// a hash of arrays
/** @type {boolean | string} */
this
.
_showHelpAfterError
=
false
;
this
.
_showSuggestionAfterError
=
false
;
// see .configureOutput() for docs
this
.
_outputConfiguration
=
{
writeOut
:
(
str
)
=>
process
.
stdout
.
write
(
str
),
writeErr
:
(
str
)
=>
process
.
stderr
.
write
(
str
),
getOutHelpWidth
:
()
=>
process
.
stdout
.
isTTY
?
process
.
stdout
.
columns
:
undefined
,
getErrHelpWidth
:
()
=>
process
.
stderr
.
isTTY
?
process
.
stderr
.
columns
:
undefined
,
outputError
:
(
str
,
write
)
=>
write
(
str
)
};
this
.
_hidden
=
false
;
this
.
_hasHelpOption
=
true
;
this
.
_helpFlags
=
'-h, --help'
;
this
.
_helpDescription
=
'display help for command'
;
this
.
_helpShortFlag
=
'-h'
;
this
.
_helpLongFlag
=
'--help'
;
this
.
_addImplicitHelpCommand
=
undefined
;
// Deliberately undefined, not decided whether true or false
this
.
_helpCommandName
=
'help'
;
this
.
_helpCommandnameAndArgs
=
'help [command]'
;
this
.
_helpCommandDescription
=
'display help for command'
;
this
.
_helpConfiguration
=
{};
}
/**
* Copy settings that are useful to have in common across root command and subcommands.
*
* (Used internally when adding a command using `.command()` so subcommands inherit parent settings.)
*
* @param {Command} sourceCommand
* @return {Command} returns `this` for executable command
*/
copyInheritedSettings
(
sourceCommand
)
{
this
.
_outputConfiguration
=
sourceCommand
.
_outputConfiguration
;
this
.
_hasHelpOption
=
sourceCommand
.
_hasHelpOption
;
this
.
_helpFlags
=
sourceCommand
.
_helpFlags
;
this
.
_helpDescription
=
sourceCommand
.
_helpDescription
;
this
.
_helpShortFlag
=
sourceCommand
.
_helpShortFlag
;
this
.
_helpLongFlag
=
sourceCommand
.
_helpLongFlag
;
this
.
_helpCommandName
=
sourceCommand
.
_helpCommandName
;
this
.
_helpCommandnameAndArgs
=
sourceCommand
.
_helpCommandnameAndArgs
;
this
.
_helpCommandDescription
=
sourceCommand
.
_helpCommandDescription
;
this
.
_helpConfiguration
=
sourceCommand
.
_helpConfiguration
;
this
.
_exitCallback
=
sourceCommand
.
_exitCallback
;
this
.
_storeOptionsAsProperties
=
sourceCommand
.
_storeOptionsAsProperties
;
this
.
_combineFlagAndOptionalValue
=
sourceCommand
.
_combineFlagAndOptionalValue
;
this
.
_allowExcessArguments
=
sourceCommand
.
_allowExcessArguments
;
this
.
_enablePositionalOptions
=
sourceCommand
.
_enablePositionalOptions
;
this
.
_showHelpAfterError
=
sourceCommand
.
_showHelpAfterError
;
this
.
_showSuggestionAfterError
=
sourceCommand
.
_showSuggestionAfterError
;
return
this
;
}
/**
* Define a command.
*
* There are two styles of command: pay attention to where to put the description.
*
* @example
* // Command implemented using action handler (description is supplied separately to `.command`)
* program
* .command('clone <source> [destination]')
* .description('clone a repository into a newly created directory')
* .action((source, destination) => {
* console.log('clone command called');
* });
*
* // Command implemented using separate executable file (description is second parameter to `.command`)
* program
* .command('start <service>', 'start named service')
* .command('stop [service]', 'stop named service, or all if no name supplied');
*
* @param {string} nameAndArgs - command name and arguments, args are `<required>` or `[optional]` and last may also be `variadic...`
* @param {Object|string} [actionOptsOrExecDesc] - configuration options (for action), or description (for executable)
* @param {Object} [execOpts] - configuration options (for executable)
* @return {Command} returns new command for action handler, or `this` for executable command
*/
command
(
nameAndArgs
,
actionOptsOrExecDesc
,
execOpts
)
{
let
desc
=
actionOptsOrExecDesc
;
let
opts
=
execOpts
;
if
(
typeof
desc
===
'object'
&&
desc
!==
null
)
{
opts
=
desc
;
desc
=
null
;
}
opts
=
opts
||
{};
const
[,
name
,
args
]
=
nameAndArgs
.
match
(
/([^ ]+) *(.*)/
);
const
cmd
=
this
.
createCommand
(
name
);
if
(
desc
)
{
cmd
.
description
(
desc
);
cmd
.
_executableHandler
=
true
;
}
if
(
opts
.
isDefault
)
this
.
_defaultCommandName
=
cmd
.
_name
;
cmd
.
_hidden
=
!!
(
opts
.
noHelp
||
opts
.
hidden
);
// noHelp is deprecated old name for hidden
cmd
.
_executableFile
=
opts
.
executableFile
||
null
;
// Custom name for executable file, set missing to null to match constructor
if
(
args
)
cmd
.
arguments
(
args
);
this
.
commands
.
push
(
cmd
);
cmd
.
parent
=
this
;
cmd
.
copyInheritedSettings
(
this
);
if
(
desc
)
return
this
;
return
cmd
;
};
/**
* Factory routine to create a new unattached command.
*
* See .command() for creating an attached subcommand, which uses this routine to
* create the command. You can override createCommand to customise subcommands.
*
* @param {string} [name]
* @return {Command} new command
*/
createCommand
(
name
)
{
return
new
Command
(
name
);
};
/**
* You can customise the help with a subclass of Help by overriding createHelp,
* or by overriding Help properties using configureHelp().
*
* @return {Help}
*/
createHelp
()
{
return
Object
.
assign
(
new
Help
(),
this
.
configureHelp
());
};
/**
* You can customise the help by overriding Help properties using configureHelp(),
* or with a subclass of Help by overriding createHelp().
*
* @param {Object} [configuration] - configuration options
* @return {Command|Object} `this` command for chaining, or stored configuration
*/
configureHelp
(
configuration
)
{
if
(
configuration
===
undefined
)
return
this
.
_helpConfiguration
;
this
.
_helpConfiguration
=
configuration
;
return
this
;
}
/**
* The default output goes to stdout and stderr. You can customise this for special
* applications. You can also customise the display of errors by overriding outputError.
*
* The configuration properties are all functions:
*
* // functions to change where being written, stdout and stderr
* writeOut(str)
* writeErr(str)
* // matching functions to specify width for wrapping help
* getOutHelpWidth()
* getErrHelpWidth()
* // functions based on what is being written out
* outputError(str, write) // used for displaying errors, and not used for displaying help
*
* @param {Object} [configuration] - configuration options
* @return {Command|Object} `this` command for chaining, or stored configuration
*/
configureOutput
(
configuration
)
{
if
(
configuration
===
undefined
)
return
this
.
_outputConfiguration
;
Object
.
assign
(
this
.
_outputConfiguration
,
configuration
);
return
this
;
}
/**
* Display the help or a custom message after an error occurs.
*
* @param {boolean|string} [displayHelp]
* @return {Command} `this` command for chaining
*/
showHelpAfterError
(
displayHelp
=
true
)
{
if
(
typeof
displayHelp
!==
'string'
)
displayHelp
=
!!
displayHelp
;
this
.
_showHelpAfterError
=
displayHelp
;
return
this
;
}
/**
* Display suggestion of similar commands for unknown commands, or options for unknown options.
*
* @param {boolean} [displaySuggestion]
* @return {Command} `this` command for chaining
*/
showSuggestionAfterError
(
displaySuggestion
=
true
)
{
this
.
_showSuggestionAfterError
=
!!
displaySuggestion
;
return
this
;
}
/**
* Add a prepared subcommand.
*
* See .command() for creating an attached subcommand which inherits settings from its parent.
*
* @param {Command} cmd - new subcommand
* @param {Object} [opts] - configuration options
* @return {Command} `this` command for chaining
*/
addCommand
(
cmd
,
opts
)
{
if
(
!
cmd
.
_name
)
throw
new
Error
(
'Command passed to .addCommand() must have a name'
);
// To keep things simple, block automatic name generation for deeply nested executables.
// Fail fast and detect when adding rather than later when parsing.
function
checkExplicitNames
(
commandArray
)
{
commandArray
.
forEach
((
cmd
)
=>
{
if
(
cmd
.
_executableHandler
&&
!
cmd
.
_executableFile
)
{
throw
new
Error
(
`
Must
specify
executableFile
for
deeply
nested
executable
:
$
{
cmd
.
name
()}
`
);
}
checkExplicitNames
(
cmd
.
commands
);
});
}
checkExplicitNames
(
cmd
.
commands
);
opts
=
opts
||
{};
if
(
opts
.
isDefault
)
this
.
_defaultCommandName
=
cmd
.
_name
;
if
(
opts
.
noHelp
||
opts
.
hidden
)
cmd
.
_hidden
=
true
;
// modifying passed command due to existing implementation
this
.
commands
.
push
(
cmd
);
cmd
.
parent
=
this
;
return
this
;
};
/**
* Factory routine to create a new unattached argument.
*
* See .argument() for creating an attached argument, which uses this routine to
* create the argument. You can override createArgument to return a custom argument.
*
* @param {string} name
* @param {string} [description]
* @return {Argument} new argument
*/
createArgument
(
name
,
description
)
{
return
new
Argument
(
name
,
description
);
};
/**
* Define argument syntax for command.
*
* The default is that the argument is required, and you can explicitly
* indicate this with <> around the name. Put [] around the name for an optional argument.
*
* @example
* program.argument('<input-file>');
* program.argument('[output-file]');
*
* @param {string} name
* @param {string} [description]
* @param {Function|*} [fn] - custom argument processing function
* @param {*} [defaultValue]
* @return {Command} `this` command for chaining
*/
argument
(
name
,
description
,
fn
,
defaultValue
)
{
const
argument
=
this
.
createArgument
(
name
,
description
);
if
(
typeof
fn
===
'function'
)
{
argument
.
default
(
defaultValue
).
argParser
(
fn
);
}
else
{
argument
.
default
(
fn
);
}
this
.
addArgument
(
argument
);
return
this
;
}
/**
* Define argument syntax for command, adding multiple at once (without descriptions).
*
* See also .argument().
*
* @example
* program.arguments('<cmd> [env]');
*
* @param {string} names
* @return {Command} `this` command for chaining
*/
arguments
(
names
)
{
names
.
split
(
/ +/
).
forEach
((
detail
)
=>
{
this
.
argument
(
detail
);
});
return
this
;
};
/**
* Define argument syntax for command, adding a prepared argument.
*
* @param {Argument} argument
* @return {Command} `this` command for chaining
*/
addArgument
(
argument
)
{
const
previousArgument
=
this
.
_args
.
slice
(
-
1
)[
0
];
if
(
previousArgument
&&
previousArgument
.
variadic
)
{
throw
new
Error
(
`
only
the
last
argument
can
be
variadic
'${previousArgument.name()}'
`
);
}
if
(
argument
.
required
&&
argument
.
defaultValue
!==
undefined
&&
argument
.
parseArg
===
undefined
)
{
throw
new
Error
(
`
a
default
value
for
a
required
argument
is
never
used
:
'${argument.name()}'
`
);
}
this
.
_args
.
push
(
argument
);
return
this
;
}
/**
* Override default decision whether to add implicit help command.
*
* addHelpCommand() // force on
* addHelpCommand(false); // force off
* addHelpCommand('help [cmd]', 'display help for [cmd]'); // force on with custom details
*
* @return {Command} `this` command for chaining
*/
addHelpCommand
(
enableOrNameAndArgs
,
description
)
{
if
(
enableOrNameAndArgs
===
false
)
{
this
.
_addImplicitHelpCommand
=
false
;
}
else
{
this
.
_addImplicitHelpCommand
=
true
;
if
(
typeof
enableOrNameAndArgs
===
'string'
)
{
this
.
_helpCommandName
=
enableOrNameAndArgs
.
split
(
' '
)[
0
];
this
.
_helpCommandnameAndArgs
=
enableOrNameAndArgs
;
}
this
.
_helpCommandDescription
=
description
||
this
.
_helpCommandDescription
;
}
return
this
;
};
/**
* @return {boolean}
* @api private
*/
_hasImplicitHelpCommand
()
{
if
(
this
.
_addImplicitHelpCommand
===
undefined
)
{
return
this
.
commands
.
length
&&
!
this
.
_actionHandler
&&
!
this
.
_findCommand
(
'help'
);
}
return
this
.
_addImplicitHelpCommand
;
};
/**
* Add hook for life cycle event.
*
* @param {string} event
* @param {Function} listener
* @return {Command} `this` command for chaining
*/
hook
(
event
,
listener
)
{
const
allowedValues
=
[
'preAction'
,
'postAction'
];
if
(
!
allowedValues
.
includes
(
event
))
{
throw
new
Error
(
`
Unexpected
value
for
event
passed
to
hook
:
'${event}'
.
Expecting
one
of
'${allowedValues.join("'
,
'")}'
`
);
}
if
(
this
.
_lifeCycleHooks
[
event
])
{
this
.
_lifeCycleHooks
[
event
].
push
(
listener
);
}
else
{
this
.
_lifeCycleHooks
[
event
]
=
[
listener
];
}
return
this
;
}
/**
* Register callback to use as replacement for calling process.exit.
*
* @param {Function} [fn] optional callback which will be passed a CommanderError, defaults to throwing
* @return {Command} `this` command for chaining
*/
exitOverride
(
fn
)
{
if
(
fn
)
{
this
.
_exitCallback
=
fn
;
}
else
{
this
.
_exitCallback
=
(
err
)
=>
{
if
(
err
.
code
!==
'commander.executeSubCommandAsync'
)
{
throw
err
;
}
else
{
// Async callback from spawn events, not useful to throw.
}
};
}
return
this
;
};
/**
* Call process.exit, and _exitCallback if defined.
*
* @param {number} exitCode exit code for using with process.exit
* @param {string} code an id string representing the error
* @param {string} message human-readable description of the error
* @return never
* @api private
*/
_exit
(
exitCode
,
code
,
message
)
{
if
(
this
.
_exitCallback
)
{
this
.
_exitCallback
(
new
CommanderError
(
exitCode
,
code
,
message
));
// Expecting this line is not reached.
}
process
.
exit
(
exitCode
);
};
/**
* Register callback `fn` for the command.
*
* @example
* program
* .command('serve')
* .description('start service')
* .action(function() {
* // do work here
* });
*
* @param {Function} fn
* @return {Command} `this` command for chaining
*/
action
(
fn
)
{
const
listener
=
(
args
)
=>
{
// The .action callback takes an extra parameter which is the command or options.
const
expectedArgsCount
=
this
.
_args
.
length
;
const
actionArgs
=
args
.
slice
(
0
,
expectedArgsCount
);
if
(
this
.
_storeOptionsAsProperties
)
{
actionArgs
[
expectedArgsCount
]
=
this
;
// backwards compatible "options"
}
else
{
actionArgs
[
expectedArgsCount
]
=
this
.
opts
();
}
actionArgs
.
push
(
this
);
return
fn
.
apply
(
this
,
actionArgs
);
};
this
.
_actionHandler
=
listener
;
return
this
;
};
/**
* Factory routine to create a new unattached option.
*
* See .option() for creating an attached option, which uses this routine to
* create the option. You can override createOption to return a custom option.
*
* @param {string} flags
* @param {string} [description]
* @return {Option} new option
*/
createOption
(
flags
,
description
)
{
return
new
Option
(
flags
,
description
);
};
/**
* Add an option.
*
* @param {Option} option
* @return {Command} `this` command for chaining
*/
addOption
(
option
)
{
const
oname
=
option
.
name
();
const
name
=
option
.
attributeName
();
let
defaultValue
=
option
.
defaultValue
;
// preassign default value for --no-*, [optional], <required>, or plain flag if boolean value
if
(
option
.
negate
||
option
.
optional
||
option
.
required
||
typeof
defaultValue
===
'boolean'
)
{
// when --no-foo we make sure default is true, unless a --foo option is already defined
if
(
option
.
negate
)
{
const
positiveLongFlag
=
option
.
long
.
replace
(
/^--no-/
,
'--'
);
defaultValue
=
this
.
_findOption
(
positiveLongFlag
)
?
this
.
getOptionValue
(
name
)
:
true
;
}
// preassign only if we have a default
if
(
defaultValue
!==
undefined
)
{
this
.
setOptionValueWithSource
(
name
,
defaultValue
,
'default'
);
}
}
// register the option
this
.
options
.
push
(
option
);
// handler for cli and env supplied values
const
handleOptionValue
=
(
val
,
invalidValueMessage
,
valueSource
)
=>
{
// Note: using closure to access lots of lexical scoped variables.
const
oldValue
=
this
.
getOptionValue
(
name
);
// custom processing
if
(
val
!==
null
&&
option
.
parseArg
)
{
try
{
val
=
option
.
parseArg
(
val
,
oldValue
===
undefined
?
defaultValue
:
oldValue
);
}
catch
(
err
)
{
if
(
err
.
code
===
'commander.invalidArgument'
)
{
const
message
=
`
$
{
invalidValueMessage
}
$
{
err
.
message
}
`
;
this
.
_displayError
(
err
.
exitCode
,
err
.
code
,
message
);
}
throw
err
;
}
}
else
if
(
val
!==
null
&&
option
.
variadic
)
{
val
=
option
.
_concatValue
(
val
,
oldValue
);
}
// unassigned or boolean value
if
(
typeof
oldValue
===
'boolean'
||
typeof
oldValue
===
'undefined'
)
{
// if no value, negate false, and we have a default, then use it!
if
(
val
==
null
)
{
this
.
setOptionValueWithSource
(
name
,
option
.
negate
?
false
:
defaultValue
||
true
,
valueSource
);
}
else
{
this
.
setOptionValueWithSource
(
name
,
val
,
valueSource
);
}
}
else
if
(
val
!==
null
)
{
// reassign
this
.
setOptionValueWithSource
(
name
,
option
.
negate
?
false
:
val
,
valueSource
);
}
};
this
.
on
(
'option:'
+
oname
,
(
val
)
=>
{
const
invalidValueMessage
=
`
error
:
option
'${option.flags}'
argument
'${val}'
is
invalid
.
`
;
handleOptionValue
(
val
,
invalidValueMessage
,
'cli'
);
});
if
(
option
.
envVar
)
{
this
.
on
(
'optionEnv:'
+
oname
,
(
val
)
=>
{
const
invalidValueMessage
=
`
error
:
option
'${option.flags}'
value
'${val}'
from
env
'${option.envVar}'
is
invalid
.
`
;
handleOptionValue
(
val
,
invalidValueMessage
,
'env'
);
});
}
return
this
;
}
/**
* Internal implementation shared by .option() and .requiredOption()
*
* @api private
*/
_optionEx
(
config
,
flags
,
description
,
fn
,
defaultValue
)
{
const
option
=
this
.
createOption
(
flags
,
description
);
option
.
makeOptionMandatory
(
!!
config
.
mandatory
);
if
(
typeof
fn
===
'function'
)
{
option
.
default
(
defaultValue
).
argParser
(
fn
);
}
else
if
(
fn
instanceof
RegExp
)
{
// deprecated
const
regex
=
fn
;
fn
=
(
val
,
def
)
=>
{
const
m
=
regex
.
exec
(
val
);
return
m
?
m
[
0
]
:
def
;
};
option
.
default
(
defaultValue
).
argParser
(
fn
);
}
else
{
option
.
default
(
fn
);
}
return
this
.
addOption
(
option
);
}
/**
* Define option with `flags`, `description` and optional
* coercion `fn`.
*
* The `flags` string contains the short and/or long flags,
* separated by comma, a pipe or space. The following are all valid
* all will output this way when `--help` is used.
*
* "-p, --pepper"
* "-p|--pepper"
* "-p --pepper"
*
* @example
* // simple boolean defaulting to undefined
* program.option('-p, --pepper', 'add pepper');
*
* program.pepper
* // => undefined
*
* --pepper
* program.pepper
* // => true
*
* // simple boolean defaulting to true (unless non-negated option is also defined)
* program.option('-C, --no-cheese', 'remove cheese');
*
* program.cheese
* // => true
*
* --no-cheese
* program.cheese
* // => false
*
* // required argument
* program.option('-C, --chdir <path>', 'change the working directory');
*
* --chdir /tmp
* program.chdir
* // => "/tmp"
*
* // optional argument
* program.option('-c, --cheese [type]', 'add cheese [marble]');
*
* @param {string} flags
* @param {string} [description]
* @param {Function|*} [fn] - custom option processing function or default value
* @param {*} [defaultValue]
* @return {Command} `this` command for chaining
*/
option
(
flags
,
description
,
fn
,
defaultValue
)
{
return
this
.
_optionEx
({},
flags
,
description
,
fn
,
defaultValue
);
};
/**
* Add a required option which must have a value after parsing. This usually means
* the option must be specified on the command line. (Otherwise the same as .option().)
*
* The `flags` string contains the short and/or long flags, separated by comma, a pipe or space.
*
* @param {string} flags
* @param {string} [description]
* @param {Function|*} [fn] - custom option processing function or default value
* @param {*} [defaultValue]
* @return {Command} `this` command for chaining
*/
requiredOption
(
flags
,
description
,
fn
,
defaultValue
)
{
return
this
.
_optionEx
({
mandatory
:
true
},
flags
,
description
,
fn
,
defaultValue
);
};
/**
* Alter parsing of short flags with optional values.
*
* @example
* // for `.option('-f,--flag [value]'):
* program.combineFlagAndOptionalValue(true); // `-f80` is treated like `--flag=80`, this is the default behaviour
* program.combineFlagAndOptionalValue(false) // `-fb` is treated like `-f -b`
*
* @param {Boolean} [combine=true] - if `true` or omitted, an optional value can be specified directly after the flag.
*/
combineFlagAndOptionalValue
(
combine
=
true
)
{
this
.
_combineFlagAndOptionalValue
=
!!
combine
;
return
this
;
};
/**
* Allow unknown options on the command line.
*
* @param {Boolean} [allowUnknown=true] - if `true` or omitted, no error will be thrown
* for unknown options.
*/
allowUnknownOption
(
allowUnknown
=
true
)
{
this
.
_allowUnknownOption
=
!!
allowUnknown
;
return
this
;
};
/**
* Allow excess command-arguments on the command line. Pass false to make excess arguments an error.
*
* @param {Boolean} [allowExcess=true] - if `true` or omitted, no error will be thrown
* for excess arguments.
*/
allowExcessArguments
(
allowExcess
=
true
)
{
this
.
_allowExcessArguments
=
!!
allowExcess
;
return
this
;
};
/**
* Enable positional options. Positional means global options are specified before subcommands which lets
* subcommands reuse the same option names, and also enables subcommands to turn on passThroughOptions.
* The default behaviour is non-positional and global options may appear anywhere on the command line.
*
* @param {Boolean} [positional=true]
*/
enablePositionalOptions
(
positional
=
true
)
{
this
.
_enablePositionalOptions
=
!!
positional
;
return
this
;
};
/**
* Pass through options that come after command-arguments rather than treat them as command-options,
* so actual command-options come before command-arguments. Turning this on for a subcommand requires
* positional options to have been enabled on the program (parent commands).
* The default behaviour is non-positional and options may appear before or after command-arguments.
*
* @param {Boolean} [passThrough=true]
* for unknown options.
*/
passThroughOptions
(
passThrough
=
true
)
{
this
.
_passThroughOptions
=
!!
passThrough
;
if
(
!!
this
.
parent
&&
passThrough
&&
!
this
.
parent
.
_enablePositionalOptions
)
{
throw
new
Error
(
'passThroughOptions can not be used without turning on enablePositionalOptions for parent command(s)'
);
}
return
this
;
};
/**
* Whether to store option values as properties on command object,
* or store separately (specify false). In both cases the option values can be accessed using .opts().
*
* @param {boolean} [storeAsProperties=true]
* @return {Command} `this` command for chaining
*/
storeOptionsAsProperties
(
storeAsProperties
=
true
)
{
this
.
_storeOptionsAsProperties
=
!!
storeAsProperties
;
if
(
this
.
options
.
length
)
{
throw
new
Error
(
'call .storeOptionsAsProperties() before adding options'
);
}
return
this
;
};
/**
* Retrieve option value.
*
* @param {string} key
* @return {Object} value
*/
getOptionValue
(
key
)
{
if
(
this
.
_storeOptionsAsProperties
)
{
return
this
[
key
];
}
return
this
.
_optionValues
[
key
];
};
/**
* Store option value.
*
* @param {string} key
* @param {Object} value
* @return {Command} `this` command for chaining
*/
setOptionValue
(
key
,
value
)
{
if
(
this
.
_storeOptionsAsProperties
)
{
this
[
key
]
=
value
;
}
else
{
this
.
_optionValues
[
key
]
=
value
;
}
return
this
;
};
/**
* Store option value and where the value came from.
*
* @param {string} key
* @param {Object} value
* @param {string} source - expected values are default/config/env/cli
* @return {Command} `this` command for chaining
*/
setOptionValueWithSource
(
key
,
value
,
source
)
{
this
.
setOptionValue
(
key
,
value
);
this
.
_optionValueSources
[
key
]
=
source
;
return
this
;
}
/**
* Get source of option value.
* Expected values are default | config | env | cli
*
* @param {string} key
* @return {string}
*/
getOptionValueSource
(
key
)
{
return
this
.
_optionValueSources
[
key
];
};
/**
* Get user arguments implied or explicit arguments.
* Side-effects: set _scriptPath if args included application, and use that to set implicit command name.
*
* @api private
*/
_prepareUserArgs
(
argv
,
parseOptions
)
{
if
(
argv
!==
undefined
&&
!
Array
.
isArray
(
argv
))
{
throw
new
Error
(
'first parameter to parse must be array or undefined'
);
}
parseOptions
=
parseOptions
||
{};
// Default to using process.argv
if
(
argv
===
undefined
)
{
argv
=
process
.
argv
;
// @ts-ignore: unknown property
if
(
process
.
versions
&&
process
.
versions
.
electron
)
{
parseOptions
.
from
=
'electron'
;
}
}
this
.
rawArgs
=
argv
.
slice
();
// make it a little easier for callers by supporting various argv conventions
let
userArgs
;
switch
(
parseOptions
.
from
)
{
case
undefined
:
case
'node'
:
this
.
_scriptPath
=
argv
[
1
];
userArgs
=
argv
.
slice
(
2
);
break
;
case
'electron'
:
// @ts-ignore: unknown property
if
(
process
.
defaultApp
)
{
this
.
_scriptPath
=
argv
[
1
];
userArgs
=
argv
.
slice
(
2
);
}
else
{
userArgs
=
argv
.
slice
(
1
);
}
break
;
case
'user'
:
userArgs
=
argv
.
slice
(
0
);
break
;
default
:
throw
new
Error
(
`
unexpected
parse
option
{
from
:
'${parseOptions.from}'
}
`
);
}
if
(
!
this
.
_scriptPath
&&
require
.
main
)
{
this
.
_scriptPath
=
require
.
main
.
filename
;
}
// Guess name, used in usage in help.
this
.
_name
=
this
.
_name
||
(
this
.
_scriptPath
&&
path
.
basename
(
this
.
_scriptPath
,
path
.
extname
(
this
.
_scriptPath
)));
return
userArgs
;
}
/**
* Parse `argv`, setting options and invoking commands when defined.
*
* The default expectation is that the arguments are from node and have the application as argv[0]
* and the script being run in argv[1], with user parameters after that.
*
* @example
* program.parse(process.argv);
* program.parse(); // implicitly use process.argv and auto-detect node vs electron conventions
* program.parse(my-args, { from: 'user' }); // just user supplied arguments, nothing special about argv[0]
*
* @param {string[]} [argv] - optional, defaults to process.argv
* @param {Object} [parseOptions] - optionally specify style of options with from: node/user/electron
* @param {string} [parseOptions.from] - where the args are from: 'node', 'user', 'electron'
* @return {Command} `this` command for chaining
*/
parse
(
argv
,
parseOptions
)
{
const
userArgs
=
this
.
_prepareUserArgs
(
argv
,
parseOptions
);
this
.
_parseCommand
([],
userArgs
);
return
this
;
};
/**
* Parse `argv`, setting options and invoking commands when defined.
*
* Use parseAsync instead of parse if any of your action handlers are async. Returns a Promise.
*
* The default expectation is that the arguments are from node and have the application as argv[0]
* and the script being run in argv[1], with user parameters after that.
*
* @example
* await program.parseAsync(process.argv);
* await program.parseAsync(); // implicitly use process.argv and auto-detect node vs electron conventions
* await program.parseAsync(my-args, { from: 'user' }); // just user supplied arguments, nothing special about argv[0]
*
* @param {string[]} [argv]
* @param {Object} [parseOptions]
* @param {string} parseOptions.from - where the args are from: 'node', 'user', 'electron'
* @return {Promise}
*/
async
parseAsync
(
argv
,
parseOptions
)
{
const
userArgs
=
this
.
_prepareUserArgs
(
argv
,
parseOptions
);
await
this
.
_parseCommand
([],
userArgs
);
return
this
;
};
/**
* Execute a sub-command executable.
*
* @api private
*/
_executeSubCommand
(
subcommand
,
args
)
{
args
=
args
.
slice
();
let
launchWithNode
=
false
;
// Use node for source targets so do not need to get permissions correct, and on Windows.
const
sourceExt
=
[
'.js'
,
'.ts'
,
'.tsx'
,
'.mjs'
,
'.cjs'
];
// Not checking for help first. Unlikely to have mandatory and executable, and can't robustly test for help flags in external command.
this
.
_checkForMissingMandatoryOptions
();
// Want the entry script as the reference for command name and directory for searching for other files.
let
scriptPath
=
this
.
_scriptPath
;
// Fallback in case not set, due to how Command created or called.
if
(
!
scriptPath
&&
require
.
main
)
{
scriptPath
=
require
.
main
.
filename
;
}
let
baseDir
;
try
{
const
resolvedLink
=
fs
.
realpathSync
(
scriptPath
);
baseDir
=
path
.
dirname
(
resolvedLink
);
}
catch
(
e
)
{
baseDir
=
'.'
;
// dummy, probably not going to find executable!
}
// name of the subcommand, like `pm-install`
let
bin
=
path
.
basename
(
scriptPath
,
path
.
extname
(
scriptPath
))
+
'-'
+
subcommand
.
_name
;
if
(
subcommand
.
_executableFile
)
{
bin
=
subcommand
.
_executableFile
;
}
const
localBin
=
path
.
join
(
baseDir
,
bin
);
if
(
fs
.
existsSync
(
localBin
))
{
// prefer local `./<bin>` to bin in the $PATH
bin
=
localBin
;
}
else
{
// Look for source files.
sourceExt
.
forEach
((
ext
)
=>
{
if
(
fs
.
existsSync
(
`
$
{
localBin
}
$
{
ext
}
`
))
{
bin
=
`
$
{
localBin
}
$
{
ext
}
`
;
}
});
}
launchWithNode
=
sourceExt
.
includes
(
path
.
extname
(
bin
));
let
proc
;
if
(
process
.
platform
!==
'win32'
)
{
if
(
launchWithNode
)
{
args
.
unshift
(
bin
);
// add executable arguments to spawn
args
=
incrementNodeInspectorPort
(
process
.
execArgv
).
concat
(
args
);
proc
=
childProcess
.
spawn
(
process
.
argv
[
0
],
args
,
{
stdio
:
'inherit'
});
}
else
{
proc
=
childProcess
.
spawn
(
bin
,
args
,
{
stdio
:
'inherit'
});
}
}
else
{
args
.
unshift
(
bin
);
// add executable arguments to spawn
args
=
incrementNodeInspectorPort
(
process
.
execArgv
).
concat
(
args
);
proc
=
childProcess
.
spawn
(
process
.
execPath
,
args
,
{
stdio
:
'inherit'
});
}
const
signals
=
[
'SIGUSR1'
,
'SIGUSR2'
,
'SIGTERM'
,
'SIGINT'
,
'SIGHUP'
];
signals
.
forEach
((
signal
)
=>
{
// @ts-ignore
process
.
on
(
signal
,
()
=>
{
if
(
proc
.
killed
===
false
&&
proc
.
exitCode
===
null
)
{
proc
.
kill
(
signal
);
}
});
});
// By default terminate process when spawned process terminates.
// Suppressing the exit if exitCallback defined is a bit messy and of limited use, but does allow process to stay running!
const
exitCallback
=
this
.
_exitCallback
;
if
(
!
exitCallback
)
{
proc
.
on
(
'close'
,
process
.
exit
.
bind
(
process
));
}
else
{
proc
.
on
(
'close'
,
()
=>
{
exitCallback
(
new
CommanderError
(
process
.
exitCode
||
0
,
'commander.executeSubCommandAsync'
,
'(close)'
));
});
}
proc
.
on
(
'error'
,
(
err
)
=>
{
// @ts-ignore
if
(
err
.
code
===
'ENOENT'
)
{
const
executableMissing
=
`
'${bin}'
does
not
exist
-
if
'${subcommand._name}'
is
not
meant
to
be
an
executable
command
,
remove
description
parameter
from
'.command()'
and
use
'.description()'
instead
-
if
the
default
executable
name
is
not
suitable
,
use
the
executableFile
option
to
supply
a
custom
name
`
;
throw
new
Error
(
executableMissing
);
// @ts-ignore
}
else
if
(
err
.
code
===
'EACCES'
)
{
throw
new
Error
(
`
'${bin}'
not
executable
`
);
}
if
(
!
exitCallback
)
{
process
.
exit
(
1
);
}
else
{
const
wrappedError
=
new
CommanderError
(
1
,
'commander.executeSubCommandAsync'
,
'(error)'
);
wrappedError
.
nestedError
=
err
;
exitCallback
(
wrappedError
);
}
});
// Store the reference to the child process
this
.
runningCommand
=
proc
;
};
/**
* @api private
*/
_dispatchSubcommand
(
commandName
,
operands
,
unknown
)
{
const
subCommand
=
this
.
_findCommand
(
commandName
);
if
(
!
subCommand
)
this
.
help
({
error
:
true
});
if
(
subCommand
.
_executableHandler
)
{
this
.
_executeSubCommand
(
subCommand
,
operands
.
concat
(
unknown
));
}
else
{
return
subCommand
.
_parseCommand
(
operands
,
unknown
);
}
};
/**
* Check this.args against expected this._args.
*
* @api private
*/
_checkNumberOfArguments
()
{
// too few
this
.
_args
.
forEach
((
arg
,
i
)
=>
{
if
(
arg
.
required
&&
this
.
args
[
i
]
==
null
)
{
this
.
missingArgument
(
arg
.
name
());
}
});
// too many
if
(
this
.
_args
.
length
>
0
&&
this
.
_args
[
this
.
_args
.
length
-
1
].
variadic
)
{
return
;
}
if
(
this
.
args
.
length
>
this
.
_args
.
length
)
{
this
.
_excessArguments
(
this
.
args
);
}
};
/**
* Process this.args using this._args and save as this.processedArgs!
*
* @api private
*/
_processArguments
()
{
const
myParseArg
=
(
argument
,
value
,
previous
)
=>
{
// Extra processing for nice error message on parsing failure.
let
parsedValue
=
value
;
if
(
value
!==
null
&&
argument
.
parseArg
)
{
try
{
parsedValue
=
argument
.
parseArg
(
value
,
previous
);
}
catch
(
err
)
{
if
(
err
.
code
===
'commander.invalidArgument'
)
{
const
message
=
`
error
:
command
-
argument
value
'${value}'
is
invalid
for
argument
'${argument.name()}'
.
$
{
err
.
message
}
`
;
this
.
_displayError
(
err
.
exitCode
,
err
.
code
,
message
);
}
throw
err
;
}
}
return
parsedValue
;
};
this
.
_checkNumberOfArguments
();
const
processedArgs
=
[];
this
.
_args
.
forEach
((
declaredArg
,
index
)
=>
{
let
value
=
declaredArg
.
defaultValue
;
if
(
declaredArg
.
variadic
)
{
// Collect together remaining arguments for passing together as an array.
if
(
index
<
this
.
args
.
length
)
{
value
=
this
.
args
.
slice
(
index
);
if
(
declaredArg
.
parseArg
)
{
value
=
value
.
reduce
((
processed
,
v
)
=>
{
return
myParseArg
(
declaredArg
,
v
,
processed
);
},
declaredArg
.
defaultValue
);
}
}
else
if
(
value
===
undefined
)
{
value
=
[];
}
}
else
if
(
index
<
this
.
args
.
length
)
{
value
=
this
.
args
[
index
];
if
(
declaredArg
.
parseArg
)
{
value
=
myParseArg
(
declaredArg
,
value
,
declaredArg
.
defaultValue
);
}
}
processedArgs
[
index
]
=
value
;
});
this
.
processedArgs
=
processedArgs
;
}
/**
* Once we have a promise we chain, but call synchronously until then.
*
* @param {Promise|undefined} promise
* @param {Function} fn
* @return {Promise|undefined}
* @api private
*/
_chainOrCall
(
promise
,
fn
)
{
// thenable
if
(
promise
&&
promise
.
then
&&
typeof
promise
.
then
===
'function'
)
{
// already have a promise, chain callback
return
promise
.
then
(()
=>
fn
());
}
// callback might return a promise
return
fn
();
}
/**
*
* @param {Promise|undefined} promise
* @param {string} event
* @return {Promise|undefined}
* @api private
*/
_chainOrCallHooks
(
promise
,
event
)
{
let
result
=
promise
;
const
hooks
=
[];
getCommandAndParents
(
this
)
.
reverse
()
.
filter
(
cmd
=>
cmd
.
_lifeCycleHooks
[
event
]
!==
undefined
)
.
forEach
(
hookedCommand
=>
{
hookedCommand
.
_lifeCycleHooks
[
event
].
forEach
((
callback
)
=>
{
hooks
.
push
({
hookedCommand
,
callback
});
});
});
if
(
event
===
'postAction'
)
{
hooks
.
reverse
();
}
hooks
.
forEach
((
hookDetail
)
=>
{
result
=
this
.
_chainOrCall
(
result
,
()
=>
{
return
hookDetail
.
callback
(
hookDetail
.
hookedCommand
,
this
);
});
});
return
result
;
}
/**
* Process arguments in context of this command.
* Returns action result, in case it is a promise.
*
* @api private
*/
_parseCommand
(
operands
,
unknown
)
{
const
parsed
=
this
.
parseOptions
(
unknown
);
this
.
_parseOptionsEnv
();
// after cli, so parseArg not called on both cli and env
operands
=
operands
.
concat
(
parsed
.
operands
);
unknown
=
parsed
.
unknown
;
this
.
args
=
operands
.
concat
(
unknown
);
if
(
operands
&&
this
.
_findCommand
(
operands
[
0
]))
{
return
this
.
_dispatchSubcommand
(
operands
[
0
],
operands
.
slice
(
1
),
unknown
);
}
if
(
this
.
_hasImplicitHelpCommand
()
&&
operands
[
0
]
===
this
.
_helpCommandName
)
{
if
(
operands
.
length
===
1
)
{
this
.
help
();
}
return
this
.
_dispatchSubcommand
(
operands
[
1
],
[],
[
this
.
_helpLongFlag
]);
}
if
(
this
.
_defaultCommandName
)
{
outputHelpIfRequested
(
this
,
unknown
);
// Run the help for default command from parent rather than passing to default command
return
this
.
_dispatchSubcommand
(
this
.
_defaultCommandName
,
operands
,
unknown
);
}
if
(
this
.
commands
.
length
&&
this
.
args
.
length
===
0
&&
!
this
.
_actionHandler
&&
!
this
.
_defaultCommandName
)
{
// probably missing subcommand and no handler, user needs help (and exit)
this
.
help
({
error
:
true
});
}
outputHelpIfRequested
(
this
,
parsed
.
unknown
);
this
.
_checkForMissingMandatoryOptions
();
// We do not always call this check to avoid masking a "better" error, like unknown command.
const
checkForUnknownOptions
=
()
=>
{
if
(
parsed
.
unknown
.
length
>
0
)
{
this
.
unknownOption
(
parsed
.
unknown
[
0
]);
}
};
const
commandEvent
=
`
command
:
$
{
this
.
name
()}
`
;
if
(
this
.
_actionHandler
)
{
checkForUnknownOptions
();
this
.
_processArguments
();
let
actionResult
;
actionResult
=
this
.
_chainOrCallHooks
(
actionResult
,
'preAction'
);
actionResult
=
this
.
_chainOrCall
(
actionResult
,
()
=>
this
.
_actionHandler
(
this
.
processedArgs
));
if
(
this
.
parent
)
this
.
parent
.
emit
(
commandEvent
,
operands
,
unknown
);
// legacy
actionResult
=
this
.
_chainOrCallHooks
(
actionResult
,
'postAction'
);
return
actionResult
;
}
if
(
this
.
parent
&&
this
.
parent
.
listenerCount
(
commandEvent
))
{
checkForUnknownOptions
();
this
.
_processArguments
();
this
.
parent
.
emit
(
commandEvent
,
operands
,
unknown
);
// legacy
}
else
if
(
operands
.
length
)
{
if
(
this
.
_findCommand
(
'*'
))
{
// legacy default command
return
this
.
_dispatchSubcommand
(
'*'
,
operands
,
unknown
);
}
if
(
this
.
listenerCount
(
'command:*'
))
{
// skip option check, emit event for possible misspelling suggestion
this
.
emit
(
'command:*'
,
operands
,
unknown
);
}
else
if
(
this
.
commands
.
length
)
{
this
.
unknownCommand
();
}
else
{
checkForUnknownOptions
();
this
.
_processArguments
();
}
}
else
if
(
this
.
commands
.
length
)
{
checkForUnknownOptions
();
// This command has subcommands and nothing hooked up at this level, so display help (and exit).
this
.
help
({
error
:
true
});
}
else
{
checkForUnknownOptions
();
this
.
_processArguments
();
// fall through for caller to handle after calling .parse()
}
};
/**
* Find matching command.
*
* @api private
*/
_findCommand
(
name
)
{
if
(
!
name
)
return
undefined
;
return
this
.
commands
.
find
(
cmd
=>
cmd
.
_name
===
name
||
cmd
.
_aliases
.
includes
(
name
));
};
/**
* Return an option matching `arg` if any.
*
* @param {string} arg
* @return {Option}
* @api private
*/
_findOption
(
arg
)
{
return
this
.
options
.
find
(
option
=>
option
.
is
(
arg
));
};
/**
* Display an error message if a mandatory option does not have a value.
* Lazy calling after checking for help flags from leaf subcommand.
*
* @api private
*/
_checkForMissingMandatoryOptions
()
{
// Walk up hierarchy so can call in subcommand after checking for displaying help.
for
(
let
cmd
=
this
;
cmd
;
cmd
=
cmd
.
parent
)
{
cmd
.
options
.
forEach
((
anOption
)
=>
{
if
(
anOption
.
mandatory
&&
(
cmd
.
getOptionValue
(
anOption
.
attributeName
())
===
undefined
))
{
cmd
.
missingMandatoryOptionValue
(
anOption
);
}
});
}
};
/**
* Parse options from `argv` removing known options,
* and return argv split into operands and unknown arguments.
*
* Examples:
*
* argv => operands, unknown
* --known kkk op => [op], []
* op --known kkk => [op], []
* sub --unknown uuu op => [sub], [--unknown uuu op]
* sub -- --unknown uuu op => [sub --unknown uuu op], []
*
* @param {String[]} argv
* @return {{operands: String[], unknown: String[]}}
*/
parseOptions
(
argv
)
{
const
operands
=
[];
// operands, not options or values
const
unknown
=
[];
// first unknown option and remaining unknown args
let
dest
=
operands
;
const
args
=
argv
.
slice
();
function
maybeOption
(
arg
)
{
return
arg
.
length
>
1
&&
arg
[
0
]
===
'-'
;
}
// parse options
let
activeVariadicOption
=
null
;
while
(
args
.
length
)
{
const
arg
=
args
.
shift
();
// literal
if
(
arg
===
'--'
)
{
if
(
dest
===
unknown
)
dest
.
push
(
arg
);
dest
.
push
(...
args
);
break
;
}
if
(
activeVariadicOption
&&
!
maybeOption
(
arg
))
{
this
.
emit
(
`
option
:
$
{
activeVariadicOption
.
name
()}
`
,
arg
);
continue
;
}
activeVariadicOption
=
null
;
if
(
maybeOption
(
arg
))
{
const
option
=
this
.
_findOption
(
arg
);
// recognised option, call listener to assign value with possible custom processing
if
(
option
)
{
if
(
option
.
required
)
{
const
value
=
args
.
shift
();
if
(
value
===
undefined
)
this
.
optionMissingArgument
(
option
);
this
.
emit
(
`
option
:
$
{
option
.
name
()}
`
,
value
);
}
else
if
(
option
.
optional
)
{
let
value
=
null
;
// historical behaviour is optional value is following arg unless an option
if
(
args
.
length
>
0
&&
!
maybeOption
(
args
[
0
]))
{
value
=
args
.
shift
();
}
this
.
emit
(
`
option
:
$
{
option
.
name
()}
`
,
value
);
}
else
{
// boolean flag
this
.
emit
(
`
option
:
$
{
option
.
name
()}
`
);
}
activeVariadicOption
=
option
.
variadic
?
option
:
null
;
continue
;
}
}
// Look for combo options following single dash, eat first one if known.
if
(
arg
.
length
>
2
&&
arg
[
0
]
===
'-'
&&
arg
[
1
]
!==
'-'
)
{
const
option
=
this
.
_findOption
(
`
-
$
{
arg
[
1
]}
`
);
if
(
option
)
{
if
(
option
.
required
||
(
option
.
optional
&&
this
.
_combineFlagAndOptionalValue
))
{
// option with value following in same argument
this
.
emit
(
`
option
:
$
{
option
.
name
()}
`
,
arg
.
slice
(
2
));
}
else
{
// boolean option, emit and put back remainder of arg for further processing
this
.
emit
(
`
option
:
$
{
option
.
name
()}
`
);
args
.
unshift
(
`
-
$
{
arg
.
slice
(
2
)}
`
);
}
continue
;
}
}
// Look for known long flag with value, like --foo=bar
if
(
/^--[^=]+=/
.
test
(
arg
))
{
const
index
=
arg
.
indexOf
(
'='
);
const
option
=
this
.
_findOption
(
arg
.
slice
(
0
,
index
));
if
(
option
&&
(
option
.
required
||
option
.
optional
))
{
this
.
emit
(
`
option
:
$
{
option
.
name
()}
`
,
arg
.
slice
(
index
+
1
));
continue
;
}
}
// Not a recognised option by this command.
// Might be a command-argument, or subcommand option, or unknown option, or help command or option.
// An unknown option means further arguments also classified as unknown so can be reprocessed by subcommands.
if
(
maybeOption
(
arg
))
{
dest
=
unknown
;
}
// If using positionalOptions, stop processing our options at subcommand.
if
((
this
.
_enablePositionalOptions
||
this
.
_passThroughOptions
)
&&
operands
.
length
===
0
&&
unknown
.
length
===
0
)
{
if
(
this
.
_findCommand
(
arg
))
{
operands
.
push
(
arg
);
if
(
args
.
length
>
0
)
unknown
.
push
(...
args
);
break
;
}
else
if
(
arg
===
this
.
_helpCommandName
&&
this
.
_hasImplicitHelpCommand
())
{
operands
.
push
(
arg
);
if
(
args
.
length
>
0
)
operands
.
push
(...
args
);
break
;
}
else
if
(
this
.
_defaultCommandName
)
{
unknown
.
push
(
arg
);
if
(
args
.
length
>
0
)
unknown
.
push
(...
args
);
break
;
}
}
// If using passThroughOptions, stop processing options at first command-argument.
if
(
this
.
_passThroughOptions
)
{
dest
.
push
(
arg
);
if
(
args
.
length
>
0
)
dest
.
push
(...
args
);
break
;
}
// add arg
dest
.
push
(
arg
);
}
return
{
operands
,
unknown
};
};
/**
* Return an object containing options as key-value pairs
*
* @return {Object}
*/
opts
()
{
if
(
this
.
_storeOptionsAsProperties
)
{
// Preserve original behaviour so backwards compatible when still using properties
const
result
=
{};
const
len
=
this
.
options
.
length
;
for
(
let
i
=
0
;
i
<
len
;
i
++
)
{
const
key
=
this
.
options
[
i
].
attributeName
();
result
[
key
]
=
key
===
this
.
_versionOptionName
?
this
.
_version
:
this
[
key
];
}
return
result
;
}
return
this
.
_optionValues
;
};
/**
* Internal bottleneck for handling of parsing errors.
*
* @api private
*/
_displayError
(
exitCode
,
code
,
message
)
{
this
.
_outputConfiguration
.
outputError
(
`
$
{
message
}
\
n
`
,
this
.
_outputConfiguration
.
writeErr
);
if
(
typeof
this
.
_showHelpAfterError
===
'string'
)
{
this
.
_outputConfiguration
.
writeErr
(
`
$
{
this
.
_showHelpAfterError
}
\
n
`
);
}
else
if
(
this
.
_showHelpAfterError
)
{
this
.
_outputConfiguration
.
writeErr
(
'\n'
);
this
.
outputHelp
({
error
:
true
});
}
this
.
_exit
(
exitCode
,
code
,
message
);
}
/**
* Apply any option related environment variables, if option does
* not have a value from cli or client code.
*
* @api private
*/
_parseOptionsEnv
()
{
this
.
options
.
forEach
((
option
)
=>
{
if
(
option
.
envVar
&&
option
.
envVar
in
process
.
env
)
{
const
optionKey
=
option
.
attributeName
();
// Priority check. Do not overwrite cli or options from unknown source (client-code).
if
(
this
.
getOptionValue
(
optionKey
)
===
undefined
||
[
'default'
,
'config'
,
'env'
].
includes
(
this
.
getOptionValueSource
(
optionKey
)))
{
if
(
option
.
required
||
option
.
optional
)
{
// option can take a value
// keep very simple, optional always takes value
this
.
emit
(
`
optionEnv
:
$
{
option
.
name
()}
`
,
process
.
env
[
option
.
envVar
]);
}
else
{
// boolean
// keep very simple, only care that envVar defined and not the value
this
.
emit
(
`
optionEnv
:
$
{
option
.
name
()}
`
);
}
}
}
});
}
/**
* Argument `name` is missing.
*
* @param {string} name
* @api private
*/
missingArgument
(
name
)
{
const
message
=
`
error
:
missing
required
argument
'${name}'
`
;
this
.
_displayError
(
1
,
'commander.missingArgument'
,
message
);
};
/**
* `Option` is missing an argument.
*
* @param {Option} option
* @api private
*/
optionMissingArgument
(
option
)
{
const
message
=
`
error
:
option
'${option.flags}'
argument
missing
`
;
this
.
_displayError
(
1
,
'commander.optionMissingArgument'
,
message
);
};
/**
* `Option` does not have a value, and is a mandatory option.
*
* @param {Option} option
* @api private
*/
missingMandatoryOptionValue
(
option
)
{
const
message
=
`
error
:
required
option
'${option.flags}'
not
specified
`
;
this
.
_displayError
(
1
,
'commander.missingMandatoryOptionValue'
,
message
);
};
/**
* Unknown option `flag`.
*
* @param {string} flag
* @api private
*/
unknownOption
(
flag
)
{
if
(
this
.
_allowUnknownOption
)
return
;
let
suggestion
=
''
;
if
(
flag
.
startsWith
(
'--'
)
&&
this
.
_showSuggestionAfterError
)
{
// Looping to pick up the global options too
let
candidateFlags
=
[];
let
command
=
this
;
do
{
const
moreFlags
=
command
.
createHelp
().
visibleOptions
(
command
)
.
filter
(
option
=>
option
.
long
)
.
map
(
option
=>
option
.
long
);
candidateFlags
=
candidateFlags
.
concat
(
moreFlags
);
command
=
command
.
parent
;
}
while
(
command
&&
!
command
.
_enablePositionalOptions
);
suggestion
=
suggestSimilar
(
flag
,
candidateFlags
);
}
const
message
=
`
error
:
unknown
option
'${flag}'
$
{
suggestion
}
`
;
this
.
_displayError
(
1
,
'commander.unknownOption'
,
message
);
};
/**
* Excess arguments, more than expected.
*
* @param {string[]} receivedArgs
* @api private
*/
_excessArguments
(
receivedArgs
)
{
if
(
this
.
_allowExcessArguments
)
return
;
const
expected
=
this
.
_args
.
length
;
const
s
=
(
expected
===
1
)
?
''
:
's'
;
const
forSubcommand
=
this
.
parent
?
`
for
'${this.name()}'
`
:
''
;
const
message
=
`
error
:
too
many
arguments$
{
forSubcommand
}.
Expected
$
{
expected
}
argument$
{
s
}
but
got
$
{
receivedArgs
.
length
}.
`
;
this
.
_displayError
(
1
,
'commander.excessArguments'
,
message
);
};
/**
* Unknown command.
*
* @api private
*/
unknownCommand
()
{
const
unknownName
=
this
.
args
[
0
];
let
suggestion
=
''
;
if
(
this
.
_showSuggestionAfterError
)
{
const
candidateNames
=
[];
this
.
createHelp
().
visibleCommands
(
this
).
forEach
((
command
)
=>
{
candidateNames
.
push
(
command
.
name
());
// just visible alias
if
(
command
.
alias
())
candidateNames
.
push
(
command
.
alias
());
});
suggestion
=
suggestSimilar
(
unknownName
,
candidateNames
);
}
const
message
=
`
error
:
unknown
command
'${unknownName}'
$
{
suggestion
}
`
;
this
.
_displayError
(
1
,
'commander.unknownCommand'
,
message
);
};
/**
* Set the program version to `str`.
*
* This method auto-registers the "-V, --version" flag
* which will print the version number when passed.
*
* You can optionally supply the flags and description to override the defaults.
*
* @param {string} str
* @param {string} [flags]
* @param {string} [description]
* @return {this | string} `this` command for chaining, or version string if no arguments
*/
version
(
str
,
flags
,
description
)
{
if
(
str
===
undefined
)
return
this
.
_version
;
this
.
_version
=
str
;
flags
=
flags
||
'-V, --version'
;
description
=
description
||
'output the version number'
;
const
versionOption
=
this
.
createOption
(
flags
,
description
);
this
.
_versionOptionName
=
versionOption
.
attributeName
();
this
.
options
.
push
(
versionOption
);
this
.
on
(
'option:'
+
versionOption
.
name
(),
()
=>
{
this
.
_outputConfiguration
.
writeOut
(
`
$
{
str
}
\
n
`
);
this
.
_exit
(
0
,
'commander.version'
,
str
);
});
return
this
;
};
/**
* Set the description to `str`.
*
* @param {string} [str]
* @param {Object} [argsDescription]
* @return {string|Command}
*/
description
(
str
,
argsDescription
)
{
if
(
str
===
undefined
&&
argsDescription
===
undefined
)
return
this
.
_description
;
this
.
_description
=
str
;
if
(
argsDescription
)
{
this
.
_argsDescription
=
argsDescription
;
}
return
this
;
};
/**
* Set an alias for the command.
*
* You may call more than once to add multiple aliases. Only the first alias is shown in the auto-generated help.
*
* @param {string} [alias]
* @return {string|Command}
*/
alias
(
alias
)
{
if
(
alias
===
undefined
)
return
this
.
_aliases
[
0
];
// just return first, for backwards compatibility
/** @type {Command} */
let
command
=
this
;
if
(
this
.
commands
.
length
!==
0
&&
this
.
commands
[
this
.
commands
.
length
-
1
].
_executableHandler
)
{
// assume adding alias for last added executable subcommand, rather than this
command
=
this
.
commands
[
this
.
commands
.
length
-
1
];
}
if
(
alias
===
command
.
_name
)
throw
new
Error
(
'Command alias can\'t be the same as its name'
);
command
.
_aliases
.
push
(
alias
);
return
this
;
};
/**
* Set aliases for the command.
*
* Only the first alias is shown in the auto-generated help.
*
* @param {string[]} [aliases]
* @return {string[]|Command}
*/
aliases
(
aliases
)
{
// Getter for the array of aliases is the main reason for having aliases() in addition to alias().
if
(
aliases
===
undefined
)
return
this
.
_aliases
;
aliases
.
forEach
((
alias
)
=>
this
.
alias
(
alias
));
return
this
;
};
/**
* Set / get the command usage `str`.
*
* @param {string} [str]
* @return {String|Command}
*/
usage
(
str
)
{
if
(
str
===
undefined
)
{
if
(
this
.
_usage
)
return
this
.
_usage
;
const
args
=
this
.
_args
.
map
((
arg
)
=>
{
return
humanReadableArgName
(
arg
);
});
return
[].
concat
(
(
this
.
options
.
length
||
this
.
_hasHelpOption
?
'[options]'
:
[]),
(
this
.
commands
.
length
?
'[command]'
:
[]),
(
this
.
_args
.
length
?
args
:
[])
).
join
(
' '
);
}
this
.
_usage
=
str
;
return
this
;
};
/**
* Get or set the name of the command
*
* @param {string} [str]
* @return {string|Command}
*/
name
(
str
)
{
if
(
str
===
undefined
)
return
this
.
_name
;
this
.
_name
=
str
;
return
this
;
};
/**
* Return program help documentation.
*
* @param {{ error: boolean }} [contextOptions] - pass {error:true} to wrap for stderr instead of stdout
* @return {string}
*/
helpInformation
(
contextOptions
)
{
const
helper
=
this
.
createHelp
();
if
(
helper
.
helpWidth
===
undefined
)
{
helper
.
helpWidth
=
(
contextOptions
&&
contextOptions
.
error
)
?
this
.
_outputConfiguration
.
getErrHelpWidth
()
:
this
.
_outputConfiguration
.
getOutHelpWidth
();
}
return
helper
.
formatHelp
(
this
,
helper
);
};
/**
* @api private
*/
_getHelpContext
(
contextOptions
)
{
contextOptions
=
contextOptions
||
{};
const
context
=
{
error
:
!!
contextOptions
.
error
};
let
write
;
if
(
context
.
error
)
{
write
=
(
arg
)
=>
this
.
_outputConfiguration
.
writeErr
(
arg
);
}
else
{
write
=
(
arg
)
=>
this
.
_outputConfiguration
.
writeOut
(
arg
);
}
context
.
write
=
contextOptions
.
write
||
write
;
context
.
command
=
this
;
return
context
;
}
/**
* Output help information for this command.
*
* Outputs built-in help, and custom text added using `.addHelpText()`.
*
* @param {{ error: boolean } | Function} [contextOptions] - pass {error:true} to write to stderr instead of stdout
*/
outputHelp
(
contextOptions
)
{
let
deprecatedCallback
;
if
(
typeof
contextOptions
===
'function'
)
{
deprecatedCallback
=
contextOptions
;
contextOptions
=
undefined
;
}
const
context
=
this
.
_getHelpContext
(
contextOptions
);
getCommandAndParents
(
this
).
reverse
().
forEach
(
command
=>
command
.
emit
(
'beforeAllHelp'
,
context
));
this
.
emit
(
'beforeHelp'
,
context
);
let
helpInformation
=
this
.
helpInformation
(
context
);
if
(
deprecatedCallback
)
{
helpInformation
=
deprecatedCallback
(
helpInformation
);
if
(
typeof
helpInformation
!==
'string'
&&
!
Buffer
.
isBuffer
(
helpInformation
))
{
throw
new
Error
(
'outputHelp callback must return a string or a Buffer'
);
}
}
context
.
write
(
helpInformation
);
this
.
emit
(
this
.
_helpLongFlag
);
// deprecated
this
.
emit
(
'afterHelp'
,
context
);
getCommandAndParents
(
this
).
forEach
(
command
=>
command
.
emit
(
'afterAllHelp'
,
context
));
};
/**
* You can pass in flags and a description to override the help
* flags and help description for your command. Pass in false to
* disable the built-in help option.
*
* @param {string | boolean} [flags]
* @param {string} [description]
* @return {Command} `this` command for chaining
*/
helpOption
(
flags
,
description
)
{
if
(
typeof
flags
===
'boolean'
)
{
this
.
_hasHelpOption
=
flags
;
return
this
;
}
this
.
_helpFlags
=
flags
||
this
.
_helpFlags
;
this
.
_helpDescription
=
description
||
this
.
_helpDescription
;
const
helpFlags
=
splitOptionFlags
(
this
.
_helpFlags
);
this
.
_helpShortFlag
=
helpFlags
.
shortFlag
;
this
.
_helpLongFlag
=
helpFlags
.
longFlag
;
return
this
;
};
/**
* Output help information and exit.
*
* Outputs built-in help, and custom text added using `.addHelpText()`.
*
* @param {{ error: boolean }} [contextOptions] - pass {error:true} to write to stderr instead of stdout
*/
help
(
contextOptions
)
{
this
.
outputHelp
(
contextOptions
);
let
exitCode
=
process
.
exitCode
||
0
;
if
(
exitCode
===
0
&&
contextOptions
&&
typeof
contextOptions
!==
'function'
&&
contextOptions
.
error
)
{
exitCode
=
1
;
}
// message: do not have all displayed text available so only passing placeholder.
this
.
_exit
(
exitCode
,
'commander.help'
,
'(outputHelp)'
);
};
/**
* Add additional text to be displayed with the built-in help.
*
* Position is 'before' or 'after' to affect just this command,
* and 'beforeAll' or 'afterAll' to affect this command and all its subcommands.
*
* @param {string} position - before or after built-in help
* @param {string | Function} text - string to add, or a function returning a string
* @return {Command} `this` command for chaining
*/
addHelpText
(
position
,
text
)
{
const
allowedValues
=
[
'beforeAll'
,
'before'
,
'after'
,
'afterAll'
];
if
(
!
allowedValues
.
includes
(
position
))
{
throw
new
Error
(
`
Unexpected
value
for
position
to
addHelpText
.
Expecting
one
of
'${allowedValues.join("'
,
'")}'
`
);
}
const
helpEvent
=
`
$
{
position
}
Help
`
;
this
.
on
(
helpEvent
,
(
context
)
=>
{
let
helpStr
;
if
(
typeof
text
===
'function'
)
{
helpStr
=
text
({
error
:
context
.
error
,
command
:
context
.
command
});
}
else
{
helpStr
=
text
;
}
// Ignore falsy value when nothing to output.
if
(
helpStr
)
{
context
.
write
(
`
$
{
helpStr
}
\
n
`
);
}
});
return
this
;
}
};
/**
* Output help information if help flags specified
*
* @param {Command} cmd - command to output help for
* @param {Array} args - array of options to search for help flags
* @api private
*/
function
outputHelpIfRequested
(
cmd
,
args
)
{
const
helpOption
=
cmd
.
_hasHelpOption
&&
args
.
find
(
arg
=>
arg
===
cmd
.
_helpLongFlag
||
arg
===
cmd
.
_helpShortFlag
);
if
(
helpOption
)
{
cmd
.
outputHelp
();
// (Do not have all displayed text available so only passing placeholder.)
cmd
.
_exit
(
0
,
'commander.helpDisplayed'
,
'(outputHelp)'
);
}
}
/**
* Scan arguments and increment port number for inspect calls (to avoid conflicts when spawning new command).
*
* @param {string[]} args - array of arguments from node.execArgv
* @returns {string[]}
* @api private
*/
function
incrementNodeInspectorPort
(
args
)
{
// Testing for these options:
// --inspect[=[host:]port]
// --inspect-brk[=[host:]port]
// --inspect-port=[host:]port
return
args
.
map
((
arg
)
=>
{
if
(
!
arg
.
startsWith
(
'--inspect'
))
{
return
arg
;
}
let
debugOption
;
let
debugHost
=
'127.0.0.1'
;
let
debugPort
=
'9229'
;
let
match
;
if
((
match
=
arg
.
match
(
/^(--inspect(-brk)?)$/
))
!==
null
)
{
// e.g. --inspect
debugOption
=
match
[
1
];
}
else
if
((
match
=
arg
.
match
(
/^(--inspect(-brk|-port)?)=([^:]+)$/
))
!==
null
)
{
debugOption
=
match
[
1
];
if
(
/^\d+$/
.
test
(
match
[
3
]))
{
// e.g. --inspect=1234
debugPort
=
match
[
3
];
}
else
{
// e.g. --inspect=localhost
debugHost
=
match
[
3
];
}
}
else
if
((
match
=
arg
.
match
(
/^(--inspect(-brk|-port)?)=([^:]+):(\d+)$/
))
!==
null
)
{
// e.g. --inspect=localhost:1234
debugOption
=
match
[
1
];
debugHost
=
match
[
3
];
debugPort
=
match
[
4
];
}
if
(
debugOption
&&
debugPort
!==
'0'
)
{
return
`
$
{
debugOption
}
=
$
{
debugHost
}
:
$
{
parseInt
(
debugPort
)
+
1
}
`
;
}
return
arg
;
});
}
/**
* @param {Command} startCommand
* @returns {Command[]}
* @api private
*/
function
getCommandAndParents
(
startCommand
)
{
const
result
=
[];
for
(
let
command
=
startCommand
;
command
;
command
=
command
.
parent
)
{
result
.
push
(
command
);
}
return
result
;
}
exports
.
Command
=
Command
;
Event Timeline
Log In to Comment