command.js 82 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662
  1. const EventEmitter = require('node:events').EventEmitter;
  2. const childProcess = require('node:child_process');
  3. const path = require('node:path');
  4. const fs = require('node:fs');
  5. const process = require('node:process');
  6. const { Argument, humanReadableArgName } = require('./argument.js');
  7. const { CommanderError } = require('./error.js');
  8. const { Help, stripColor } = require('./help.js');
  9. const { Option, DualOptions } = require('./option.js');
  10. const { suggestSimilar } = require('./suggestSimilar');
  11. class Command extends EventEmitter {
  12. /**
  13. * Initialize a new `Command`.
  14. *
  15. * @param {string} [name]
  16. */
  17. constructor(name) {
  18. super();
  19. /** @type {Command[]} */
  20. this.commands = [];
  21. /** @type {Option[]} */
  22. this.options = [];
  23. this.parent = null;
  24. this._allowUnknownOption = false;
  25. this._allowExcessArguments = false;
  26. /** @type {Argument[]} */
  27. this.registeredArguments = [];
  28. this._args = this.registeredArguments; // deprecated old name
  29. /** @type {string[]} */
  30. this.args = []; // cli args with options removed
  31. this.rawArgs = [];
  32. this.processedArgs = []; // like .args but after custom processing and collecting variadic
  33. this._scriptPath = null;
  34. this._name = name || '';
  35. this._optionValues = {};
  36. this._optionValueSources = {}; // default, env, cli etc
  37. this._storeOptionsAsProperties = false;
  38. this._actionHandler = null;
  39. this._executableHandler = false;
  40. this._executableFile = null; // custom name for executable
  41. this._executableDir = null; // custom search directory for subcommands
  42. this._defaultCommandName = null;
  43. this._exitCallback = null;
  44. this._aliases = [];
  45. this._combineFlagAndOptionalValue = true;
  46. this._description = '';
  47. this._summary = '';
  48. this._argsDescription = undefined; // legacy
  49. this._enablePositionalOptions = false;
  50. this._passThroughOptions = false;
  51. this._lifeCycleHooks = {}; // a hash of arrays
  52. /** @type {(boolean | string)} */
  53. this._showHelpAfterError = false;
  54. this._showSuggestionAfterError = true;
  55. this._savedState = null; // used in save/restoreStateBeforeParse
  56. // see configureOutput() for docs
  57. this._outputConfiguration = {
  58. writeOut: (str) => process.stdout.write(str),
  59. writeErr: (str) => process.stderr.write(str),
  60. outputError: (str, write) => write(str),
  61. getOutHelpWidth: () =>
  62. process.stdout.isTTY ? process.stdout.columns : undefined,
  63. getErrHelpWidth: () =>
  64. process.stderr.isTTY ? process.stderr.columns : undefined,
  65. getOutHasColors: () =>
  66. useColor() ?? (process.stdout.isTTY && process.stdout.hasColors?.()),
  67. getErrHasColors: () =>
  68. useColor() ?? (process.stderr.isTTY && process.stderr.hasColors?.()),
  69. stripColor: (str) => stripColor(str),
  70. };
  71. this._hidden = false;
  72. /** @type {(Option | null | undefined)} */
  73. this._helpOption = undefined; // Lazy created on demand. May be null if help option is disabled.
  74. this._addImplicitHelpCommand = undefined; // undecided whether true or false yet, not inherited
  75. /** @type {Command} */
  76. this._helpCommand = undefined; // lazy initialised, inherited
  77. this._helpConfiguration = {};
  78. }
  79. /**
  80. * Copy settings that are useful to have in common across root command and subcommands.
  81. *
  82. * (Used internally when adding a command using `.command()` so subcommands inherit parent settings.)
  83. *
  84. * @param {Command} sourceCommand
  85. * @return {Command} `this` command for chaining
  86. */
  87. copyInheritedSettings(sourceCommand) {
  88. this._outputConfiguration = sourceCommand._outputConfiguration;
  89. this._helpOption = sourceCommand._helpOption;
  90. this._helpCommand = sourceCommand._helpCommand;
  91. this._helpConfiguration = sourceCommand._helpConfiguration;
  92. this._exitCallback = sourceCommand._exitCallback;
  93. this._storeOptionsAsProperties = sourceCommand._storeOptionsAsProperties;
  94. this._combineFlagAndOptionalValue =
  95. sourceCommand._combineFlagAndOptionalValue;
  96. this._allowExcessArguments = sourceCommand._allowExcessArguments;
  97. this._enablePositionalOptions = sourceCommand._enablePositionalOptions;
  98. this._showHelpAfterError = sourceCommand._showHelpAfterError;
  99. this._showSuggestionAfterError = sourceCommand._showSuggestionAfterError;
  100. return this;
  101. }
  102. /**
  103. * @returns {Command[]}
  104. * @private
  105. */
  106. _getCommandAndAncestors() {
  107. const result = [];
  108. // eslint-disable-next-line @typescript-eslint/no-this-alias
  109. for (let command = this; command; command = command.parent) {
  110. result.push(command);
  111. }
  112. return result;
  113. }
  114. /**
  115. * Define a command.
  116. *
  117. * There are two styles of command: pay attention to where to put the description.
  118. *
  119. * @example
  120. * // Command implemented using action handler (description is supplied separately to `.command`)
  121. * program
  122. * .command('clone <source> [destination]')
  123. * .description('clone a repository into a newly created directory')
  124. * .action((source, destination) => {
  125. * console.log('clone command called');
  126. * });
  127. *
  128. * // Command implemented using separate executable file (description is second parameter to `.command`)
  129. * program
  130. * .command('start <service>', 'start named service')
  131. * .command('stop [service]', 'stop named service, or all if no name supplied');
  132. *
  133. * @param {string} nameAndArgs - command name and arguments, args are `<required>` or `[optional]` and last may also be `variadic...`
  134. * @param {(object | string)} [actionOptsOrExecDesc] - configuration options (for action), or description (for executable)
  135. * @param {object} [execOpts] - configuration options (for executable)
  136. * @return {Command} returns new command for action handler, or `this` for executable command
  137. */
  138. command(nameAndArgs, actionOptsOrExecDesc, execOpts) {
  139. let desc = actionOptsOrExecDesc;
  140. let opts = execOpts;
  141. if (typeof desc === 'object' && desc !== null) {
  142. opts = desc;
  143. desc = null;
  144. }
  145. opts = opts || {};
  146. const [, name, args] = nameAndArgs.match(/([^ ]+) *(.*)/);
  147. const cmd = this.createCommand(name);
  148. if (desc) {
  149. cmd.description(desc);
  150. cmd._executableHandler = true;
  151. }
  152. if (opts.isDefault) this._defaultCommandName = cmd._name;
  153. cmd._hidden = !!(opts.noHelp || opts.hidden); // noHelp is deprecated old name for hidden
  154. cmd._executableFile = opts.executableFile || null; // Custom name for executable file, set missing to null to match constructor
  155. if (args) cmd.arguments(args);
  156. this._registerCommand(cmd);
  157. cmd.parent = this;
  158. cmd.copyInheritedSettings(this);
  159. if (desc) return this;
  160. return cmd;
  161. }
  162. /**
  163. * Factory routine to create a new unattached command.
  164. *
  165. * See .command() for creating an attached subcommand, which uses this routine to
  166. * create the command. You can override createCommand to customise subcommands.
  167. *
  168. * @param {string} [name]
  169. * @return {Command} new command
  170. */
  171. createCommand(name) {
  172. return new Command(name);
  173. }
  174. /**
  175. * You can customise the help with a subclass of Help by overriding createHelp,
  176. * or by overriding Help properties using configureHelp().
  177. *
  178. * @return {Help}
  179. */
  180. createHelp() {
  181. return Object.assign(new Help(), this.configureHelp());
  182. }
  183. /**
  184. * You can customise the help by overriding Help properties using configureHelp(),
  185. * or with a subclass of Help by overriding createHelp().
  186. *
  187. * @param {object} [configuration] - configuration options
  188. * @return {(Command | object)} `this` command for chaining, or stored configuration
  189. */
  190. configureHelp(configuration) {
  191. if (configuration === undefined) return this._helpConfiguration;
  192. this._helpConfiguration = configuration;
  193. return this;
  194. }
  195. /**
  196. * The default output goes to stdout and stderr. You can customise this for special
  197. * applications. You can also customise the display of errors by overriding outputError.
  198. *
  199. * The configuration properties are all functions:
  200. *
  201. * // change how output being written, defaults to stdout and stderr
  202. * writeOut(str)
  203. * writeErr(str)
  204. * // change how output being written for errors, defaults to writeErr
  205. * outputError(str, write) // used for displaying errors and not used for displaying help
  206. * // specify width for wrapping help
  207. * getOutHelpWidth()
  208. * getErrHelpWidth()
  209. * // color support, currently only used with Help
  210. * getOutHasColors()
  211. * getErrHasColors()
  212. * stripColor() // used to remove ANSI escape codes if output does not have colors
  213. *
  214. * @param {object} [configuration] - configuration options
  215. * @return {(Command | object)} `this` command for chaining, or stored configuration
  216. */
  217. configureOutput(configuration) {
  218. if (configuration === undefined) return this._outputConfiguration;
  219. Object.assign(this._outputConfiguration, configuration);
  220. return this;
  221. }
  222. /**
  223. * Display the help or a custom message after an error occurs.
  224. *
  225. * @param {(boolean|string)} [displayHelp]
  226. * @return {Command} `this` command for chaining
  227. */
  228. showHelpAfterError(displayHelp = true) {
  229. if (typeof displayHelp !== 'string') displayHelp = !!displayHelp;
  230. this._showHelpAfterError = displayHelp;
  231. return this;
  232. }
  233. /**
  234. * Display suggestion of similar commands for unknown commands, or options for unknown options.
  235. *
  236. * @param {boolean} [displaySuggestion]
  237. * @return {Command} `this` command for chaining
  238. */
  239. showSuggestionAfterError(displaySuggestion = true) {
  240. this._showSuggestionAfterError = !!displaySuggestion;
  241. return this;
  242. }
  243. /**
  244. * Add a prepared subcommand.
  245. *
  246. * See .command() for creating an attached subcommand which inherits settings from its parent.
  247. *
  248. * @param {Command} cmd - new subcommand
  249. * @param {object} [opts] - configuration options
  250. * @return {Command} `this` command for chaining
  251. */
  252. addCommand(cmd, opts) {
  253. if (!cmd._name) {
  254. throw new Error(`Command passed to .addCommand() must have a name
  255. - specify the name in Command constructor or using .name()`);
  256. }
  257. opts = opts || {};
  258. if (opts.isDefault) this._defaultCommandName = cmd._name;
  259. if (opts.noHelp || opts.hidden) cmd._hidden = true; // modifying passed command due to existing implementation
  260. this._registerCommand(cmd);
  261. cmd.parent = this;
  262. cmd._checkForBrokenPassThrough();
  263. return this;
  264. }
  265. /**
  266. * Factory routine to create a new unattached argument.
  267. *
  268. * See .argument() for creating an attached argument, which uses this routine to
  269. * create the argument. You can override createArgument to return a custom argument.
  270. *
  271. * @param {string} name
  272. * @param {string} [description]
  273. * @return {Argument} new argument
  274. */
  275. createArgument(name, description) {
  276. return new Argument(name, description);
  277. }
  278. /**
  279. * Define argument syntax for command.
  280. *
  281. * The default is that the argument is required, and you can explicitly
  282. * indicate this with <> around the name. Put [] around the name for an optional argument.
  283. *
  284. * @example
  285. * program.argument('<input-file>');
  286. * program.argument('[output-file]');
  287. *
  288. * @param {string} name
  289. * @param {string} [description]
  290. * @param {(Function|*)} [fn] - custom argument processing function
  291. * @param {*} [defaultValue]
  292. * @return {Command} `this` command for chaining
  293. */
  294. argument(name, description, fn, defaultValue) {
  295. const argument = this.createArgument(name, description);
  296. if (typeof fn === 'function') {
  297. argument.default(defaultValue).argParser(fn);
  298. } else {
  299. argument.default(fn);
  300. }
  301. this.addArgument(argument);
  302. return this;
  303. }
  304. /**
  305. * Define argument syntax for command, adding multiple at once (without descriptions).
  306. *
  307. * See also .argument().
  308. *
  309. * @example
  310. * program.arguments('<cmd> [env]');
  311. *
  312. * @param {string} names
  313. * @return {Command} `this` command for chaining
  314. */
  315. arguments(names) {
  316. names
  317. .trim()
  318. .split(/ +/)
  319. .forEach((detail) => {
  320. this.argument(detail);
  321. });
  322. return this;
  323. }
  324. /**
  325. * Define argument syntax for command, adding a prepared argument.
  326. *
  327. * @param {Argument} argument
  328. * @return {Command} `this` command for chaining
  329. */
  330. addArgument(argument) {
  331. const previousArgument = this.registeredArguments.slice(-1)[0];
  332. if (previousArgument && previousArgument.variadic) {
  333. throw new Error(
  334. `only the last argument can be variadic '${previousArgument.name()}'`,
  335. );
  336. }
  337. if (
  338. argument.required &&
  339. argument.defaultValue !== undefined &&
  340. argument.parseArg === undefined
  341. ) {
  342. throw new Error(
  343. `a default value for a required argument is never used: '${argument.name()}'`,
  344. );
  345. }
  346. this.registeredArguments.push(argument);
  347. return this;
  348. }
  349. /**
  350. * Customise or override default help command. By default a help command is automatically added if your command has subcommands.
  351. *
  352. * @example
  353. * program.helpCommand('help [cmd]');
  354. * program.helpCommand('help [cmd]', 'show help');
  355. * program.helpCommand(false); // suppress default help command
  356. * program.helpCommand(true); // add help command even if no subcommands
  357. *
  358. * @param {string|boolean} enableOrNameAndArgs - enable with custom name and/or arguments, or boolean to override whether added
  359. * @param {string} [description] - custom description
  360. * @return {Command} `this` command for chaining
  361. */
  362. helpCommand(enableOrNameAndArgs, description) {
  363. if (typeof enableOrNameAndArgs === 'boolean') {
  364. this._addImplicitHelpCommand = enableOrNameAndArgs;
  365. return this;
  366. }
  367. enableOrNameAndArgs = enableOrNameAndArgs ?? 'help [command]';
  368. const [, helpName, helpArgs] = enableOrNameAndArgs.match(/([^ ]+) *(.*)/);
  369. const helpDescription = description ?? 'display help for command';
  370. const helpCommand = this.createCommand(helpName);
  371. helpCommand.helpOption(false);
  372. if (helpArgs) helpCommand.arguments(helpArgs);
  373. if (helpDescription) helpCommand.description(helpDescription);
  374. this._addImplicitHelpCommand = true;
  375. this._helpCommand = helpCommand;
  376. return this;
  377. }
  378. /**
  379. * Add prepared custom help command.
  380. *
  381. * @param {(Command|string|boolean)} helpCommand - custom help command, or deprecated enableOrNameAndArgs as for `.helpCommand()`
  382. * @param {string} [deprecatedDescription] - deprecated custom description used with custom name only
  383. * @return {Command} `this` command for chaining
  384. */
  385. addHelpCommand(helpCommand, deprecatedDescription) {
  386. // If not passed an object, call through to helpCommand for backwards compatibility,
  387. // as addHelpCommand was originally used like helpCommand is now.
  388. if (typeof helpCommand !== 'object') {
  389. this.helpCommand(helpCommand, deprecatedDescription);
  390. return this;
  391. }
  392. this._addImplicitHelpCommand = true;
  393. this._helpCommand = helpCommand;
  394. return this;
  395. }
  396. /**
  397. * Lazy create help command.
  398. *
  399. * @return {(Command|null)}
  400. * @package
  401. */
  402. _getHelpCommand() {
  403. const hasImplicitHelpCommand =
  404. this._addImplicitHelpCommand ??
  405. (this.commands.length &&
  406. !this._actionHandler &&
  407. !this._findCommand('help'));
  408. if (hasImplicitHelpCommand) {
  409. if (this._helpCommand === undefined) {
  410. this.helpCommand(undefined, undefined); // use default name and description
  411. }
  412. return this._helpCommand;
  413. }
  414. return null;
  415. }
  416. /**
  417. * Add hook for life cycle event.
  418. *
  419. * @param {string} event
  420. * @param {Function} listener
  421. * @return {Command} `this` command for chaining
  422. */
  423. hook(event, listener) {
  424. const allowedValues = ['preSubcommand', 'preAction', 'postAction'];
  425. if (!allowedValues.includes(event)) {
  426. throw new Error(`Unexpected value for event passed to hook : '${event}'.
  427. Expecting one of '${allowedValues.join("', '")}'`);
  428. }
  429. if (this._lifeCycleHooks[event]) {
  430. this._lifeCycleHooks[event].push(listener);
  431. } else {
  432. this._lifeCycleHooks[event] = [listener];
  433. }
  434. return this;
  435. }
  436. /**
  437. * Register callback to use as replacement for calling process.exit.
  438. *
  439. * @param {Function} [fn] optional callback which will be passed a CommanderError, defaults to throwing
  440. * @return {Command} `this` command for chaining
  441. */
  442. exitOverride(fn) {
  443. if (fn) {
  444. this._exitCallback = fn;
  445. } else {
  446. this._exitCallback = (err) => {
  447. if (err.code !== 'commander.executeSubCommandAsync') {
  448. throw err;
  449. } else {
  450. // Async callback from spawn events, not useful to throw.
  451. }
  452. };
  453. }
  454. return this;
  455. }
  456. /**
  457. * Call process.exit, and _exitCallback if defined.
  458. *
  459. * @param {number} exitCode exit code for using with process.exit
  460. * @param {string} code an id string representing the error
  461. * @param {string} message human-readable description of the error
  462. * @return never
  463. * @private
  464. */
  465. _exit(exitCode, code, message) {
  466. if (this._exitCallback) {
  467. this._exitCallback(new CommanderError(exitCode, code, message));
  468. // Expecting this line is not reached.
  469. }
  470. process.exit(exitCode);
  471. }
  472. /**
  473. * Register callback `fn` for the command.
  474. *
  475. * @example
  476. * program
  477. * .command('serve')
  478. * .description('start service')
  479. * .action(function() {
  480. * // do work here
  481. * });
  482. *
  483. * @param {Function} fn
  484. * @return {Command} `this` command for chaining
  485. */
  486. action(fn) {
  487. const listener = (args) => {
  488. // The .action callback takes an extra parameter which is the command or options.
  489. const expectedArgsCount = this.registeredArguments.length;
  490. const actionArgs = args.slice(0, expectedArgsCount);
  491. if (this._storeOptionsAsProperties) {
  492. actionArgs[expectedArgsCount] = this; // backwards compatible "options"
  493. } else {
  494. actionArgs[expectedArgsCount] = this.opts();
  495. }
  496. actionArgs.push(this);
  497. return fn.apply(this, actionArgs);
  498. };
  499. this._actionHandler = listener;
  500. return this;
  501. }
  502. /**
  503. * Factory routine to create a new unattached option.
  504. *
  505. * See .option() for creating an attached option, which uses this routine to
  506. * create the option. You can override createOption to return a custom option.
  507. *
  508. * @param {string} flags
  509. * @param {string} [description]
  510. * @return {Option} new option
  511. */
  512. createOption(flags, description) {
  513. return new Option(flags, description);
  514. }
  515. /**
  516. * Wrap parseArgs to catch 'commander.invalidArgument'.
  517. *
  518. * @param {(Option | Argument)} target
  519. * @param {string} value
  520. * @param {*} previous
  521. * @param {string} invalidArgumentMessage
  522. * @private
  523. */
  524. _callParseArg(target, value, previous, invalidArgumentMessage) {
  525. try {
  526. return target.parseArg(value, previous);
  527. } catch (err) {
  528. if (err.code === 'commander.invalidArgument') {
  529. const message = `${invalidArgumentMessage} ${err.message}`;
  530. this.error(message, { exitCode: err.exitCode, code: err.code });
  531. }
  532. throw err;
  533. }
  534. }
  535. /**
  536. * Check for option flag conflicts.
  537. * Register option if no conflicts found, or throw on conflict.
  538. *
  539. * @param {Option} option
  540. * @private
  541. */
  542. _registerOption(option) {
  543. const matchingOption =
  544. (option.short && this._findOption(option.short)) ||
  545. (option.long && this._findOption(option.long));
  546. if (matchingOption) {
  547. const matchingFlag =
  548. option.long && this._findOption(option.long)
  549. ? option.long
  550. : option.short;
  551. throw new Error(`Cannot add option '${option.flags}'${this._name && ` to command '${this._name}'`} due to conflicting flag '${matchingFlag}'
  552. - already used by option '${matchingOption.flags}'`);
  553. }
  554. this.options.push(option);
  555. }
  556. /**
  557. * Check for command name and alias conflicts with existing commands.
  558. * Register command if no conflicts found, or throw on conflict.
  559. *
  560. * @param {Command} command
  561. * @private
  562. */
  563. _registerCommand(command) {
  564. const knownBy = (cmd) => {
  565. return [cmd.name()].concat(cmd.aliases());
  566. };
  567. const alreadyUsed = knownBy(command).find((name) =>
  568. this._findCommand(name),
  569. );
  570. if (alreadyUsed) {
  571. const existingCmd = knownBy(this._findCommand(alreadyUsed)).join('|');
  572. const newCmd = knownBy(command).join('|');
  573. throw new Error(
  574. `cannot add command '${newCmd}' as already have command '${existingCmd}'`,
  575. );
  576. }
  577. this.commands.push(command);
  578. }
  579. /**
  580. * Add an option.
  581. *
  582. * @param {Option} option
  583. * @return {Command} `this` command for chaining
  584. */
  585. addOption(option) {
  586. this._registerOption(option);
  587. const oname = option.name();
  588. const name = option.attributeName();
  589. // store default value
  590. if (option.negate) {
  591. // --no-foo is special and defaults foo to true, unless a --foo option is already defined
  592. const positiveLongFlag = option.long.replace(/^--no-/, '--');
  593. if (!this._findOption(positiveLongFlag)) {
  594. this.setOptionValueWithSource(
  595. name,
  596. option.defaultValue === undefined ? true : option.defaultValue,
  597. 'default',
  598. );
  599. }
  600. } else if (option.defaultValue !== undefined) {
  601. this.setOptionValueWithSource(name, option.defaultValue, 'default');
  602. }
  603. // handler for cli and env supplied values
  604. const handleOptionValue = (val, invalidValueMessage, valueSource) => {
  605. // val is null for optional option used without an optional-argument.
  606. // val is undefined for boolean and negated option.
  607. if (val == null && option.presetArg !== undefined) {
  608. val = option.presetArg;
  609. }
  610. // custom processing
  611. const oldValue = this.getOptionValue(name);
  612. if (val !== null && option.parseArg) {
  613. val = this._callParseArg(option, val, oldValue, invalidValueMessage);
  614. } else if (val !== null && option.variadic) {
  615. val = option._concatValue(val, oldValue);
  616. }
  617. // Fill-in appropriate missing values. Long winded but easy to follow.
  618. if (val == null) {
  619. if (option.negate) {
  620. val = false;
  621. } else if (option.isBoolean() || option.optional) {
  622. val = true;
  623. } else {
  624. val = ''; // not normal, parseArg might have failed or be a mock function for testing
  625. }
  626. }
  627. this.setOptionValueWithSource(name, val, valueSource);
  628. };
  629. this.on('option:' + oname, (val) => {
  630. const invalidValueMessage = `error: option '${option.flags}' argument '${val}' is invalid.`;
  631. handleOptionValue(val, invalidValueMessage, 'cli');
  632. });
  633. if (option.envVar) {
  634. this.on('optionEnv:' + oname, (val) => {
  635. const invalidValueMessage = `error: option '${option.flags}' value '${val}' from env '${option.envVar}' is invalid.`;
  636. handleOptionValue(val, invalidValueMessage, 'env');
  637. });
  638. }
  639. return this;
  640. }
  641. /**
  642. * Internal implementation shared by .option() and .requiredOption()
  643. *
  644. * @return {Command} `this` command for chaining
  645. * @private
  646. */
  647. _optionEx(config, flags, description, fn, defaultValue) {
  648. if (typeof flags === 'object' && flags instanceof Option) {
  649. throw new Error(
  650. 'To add an Option object use addOption() instead of option() or requiredOption()',
  651. );
  652. }
  653. const option = this.createOption(flags, description);
  654. option.makeOptionMandatory(!!config.mandatory);
  655. if (typeof fn === 'function') {
  656. option.default(defaultValue).argParser(fn);
  657. } else if (fn instanceof RegExp) {
  658. // deprecated
  659. const regex = fn;
  660. fn = (val, def) => {
  661. const m = regex.exec(val);
  662. return m ? m[0] : def;
  663. };
  664. option.default(defaultValue).argParser(fn);
  665. } else {
  666. option.default(fn);
  667. }
  668. return this.addOption(option);
  669. }
  670. /**
  671. * Define option with `flags`, `description`, and optional argument parsing function or `defaultValue` or both.
  672. *
  673. * The `flags` string contains the short and/or long flags, separated by comma, a pipe or space. A required
  674. * option-argument is indicated by `<>` and an optional option-argument by `[]`.
  675. *
  676. * See the README for more details, and see also addOption() and requiredOption().
  677. *
  678. * @example
  679. * program
  680. * .option('-p, --pepper', 'add pepper')
  681. * .option('--pt, --pizza-type <TYPE>', 'type of pizza') // required option-argument
  682. * .option('-c, --cheese [CHEESE]', 'add extra cheese', 'mozzarella') // optional option-argument with default
  683. * .option('-t, --tip <VALUE>', 'add tip to purchase cost', parseFloat) // custom parse function
  684. *
  685. * @param {string} flags
  686. * @param {string} [description]
  687. * @param {(Function|*)} [parseArg] - custom option processing function or default value
  688. * @param {*} [defaultValue]
  689. * @return {Command} `this` command for chaining
  690. */
  691. option(flags, description, parseArg, defaultValue) {
  692. return this._optionEx({}, flags, description, parseArg, defaultValue);
  693. }
  694. /**
  695. * Add a required option which must have a value after parsing. This usually means
  696. * the option must be specified on the command line. (Otherwise the same as .option().)
  697. *
  698. * The `flags` string contains the short and/or long flags, separated by comma, a pipe or space.
  699. *
  700. * @param {string} flags
  701. * @param {string} [description]
  702. * @param {(Function|*)} [parseArg] - custom option processing function or default value
  703. * @param {*} [defaultValue]
  704. * @return {Command} `this` command for chaining
  705. */
  706. requiredOption(flags, description, parseArg, defaultValue) {
  707. return this._optionEx(
  708. { mandatory: true },
  709. flags,
  710. description,
  711. parseArg,
  712. defaultValue,
  713. );
  714. }
  715. /**
  716. * Alter parsing of short flags with optional values.
  717. *
  718. * @example
  719. * // for `.option('-f,--flag [value]'):
  720. * program.combineFlagAndOptionalValue(true); // `-f80` is treated like `--flag=80`, this is the default behaviour
  721. * program.combineFlagAndOptionalValue(false) // `-fb` is treated like `-f -b`
  722. *
  723. * @param {boolean} [combine] - if `true` or omitted, an optional value can be specified directly after the flag.
  724. * @return {Command} `this` command for chaining
  725. */
  726. combineFlagAndOptionalValue(combine = true) {
  727. this._combineFlagAndOptionalValue = !!combine;
  728. return this;
  729. }
  730. /**
  731. * Allow unknown options on the command line.
  732. *
  733. * @param {boolean} [allowUnknown] - if `true` or omitted, no error will be thrown for unknown options.
  734. * @return {Command} `this` command for chaining
  735. */
  736. allowUnknownOption(allowUnknown = true) {
  737. this._allowUnknownOption = !!allowUnknown;
  738. return this;
  739. }
  740. /**
  741. * Allow excess command-arguments on the command line. Pass false to make excess arguments an error.
  742. *
  743. * @param {boolean} [allowExcess] - if `true` or omitted, no error will be thrown for excess arguments.
  744. * @return {Command} `this` command for chaining
  745. */
  746. allowExcessArguments(allowExcess = true) {
  747. this._allowExcessArguments = !!allowExcess;
  748. return this;
  749. }
  750. /**
  751. * Enable positional options. Positional means global options are specified before subcommands which lets
  752. * subcommands reuse the same option names, and also enables subcommands to turn on passThroughOptions.
  753. * The default behaviour is non-positional and global options may appear anywhere on the command line.
  754. *
  755. * @param {boolean} [positional]
  756. * @return {Command} `this` command for chaining
  757. */
  758. enablePositionalOptions(positional = true) {
  759. this._enablePositionalOptions = !!positional;
  760. return this;
  761. }
  762. /**
  763. * Pass through options that come after command-arguments rather than treat them as command-options,
  764. * so actual command-options come before command-arguments. Turning this on for a subcommand requires
  765. * positional options to have been enabled on the program (parent commands).
  766. * The default behaviour is non-positional and options may appear before or after command-arguments.
  767. *
  768. * @param {boolean} [passThrough] for unknown options.
  769. * @return {Command} `this` command for chaining
  770. */
  771. passThroughOptions(passThrough = true) {
  772. this._passThroughOptions = !!passThrough;
  773. this._checkForBrokenPassThrough();
  774. return this;
  775. }
  776. /**
  777. * @private
  778. */
  779. _checkForBrokenPassThrough() {
  780. if (
  781. this.parent &&
  782. this._passThroughOptions &&
  783. !this.parent._enablePositionalOptions
  784. ) {
  785. throw new Error(
  786. `passThroughOptions cannot be used for '${this._name}' without turning on enablePositionalOptions for parent command(s)`,
  787. );
  788. }
  789. }
  790. /**
  791. * Whether to store option values as properties on command object,
  792. * or store separately (specify false). In both cases the option values can be accessed using .opts().
  793. *
  794. * @param {boolean} [storeAsProperties=true]
  795. * @return {Command} `this` command for chaining
  796. */
  797. storeOptionsAsProperties(storeAsProperties = true) {
  798. if (this.options.length) {
  799. throw new Error('call .storeOptionsAsProperties() before adding options');
  800. }
  801. if (Object.keys(this._optionValues).length) {
  802. throw new Error(
  803. 'call .storeOptionsAsProperties() before setting option values',
  804. );
  805. }
  806. this._storeOptionsAsProperties = !!storeAsProperties;
  807. return this;
  808. }
  809. /**
  810. * Retrieve option value.
  811. *
  812. * @param {string} key
  813. * @return {object} value
  814. */
  815. getOptionValue(key) {
  816. if (this._storeOptionsAsProperties) {
  817. return this[key];
  818. }
  819. return this._optionValues[key];
  820. }
  821. /**
  822. * Store option value.
  823. *
  824. * @param {string} key
  825. * @param {object} value
  826. * @return {Command} `this` command for chaining
  827. */
  828. setOptionValue(key, value) {
  829. return this.setOptionValueWithSource(key, value, undefined);
  830. }
  831. /**
  832. * Store option value and where the value came from.
  833. *
  834. * @param {string} key
  835. * @param {object} value
  836. * @param {string} source - expected values are default/config/env/cli/implied
  837. * @return {Command} `this` command for chaining
  838. */
  839. setOptionValueWithSource(key, value, source) {
  840. if (this._storeOptionsAsProperties) {
  841. this[key] = value;
  842. } else {
  843. this._optionValues[key] = value;
  844. }
  845. this._optionValueSources[key] = source;
  846. return this;
  847. }
  848. /**
  849. * Get source of option value.
  850. * Expected values are default | config | env | cli | implied
  851. *
  852. * @param {string} key
  853. * @return {string}
  854. */
  855. getOptionValueSource(key) {
  856. return this._optionValueSources[key];
  857. }
  858. /**
  859. * Get source of option value. See also .optsWithGlobals().
  860. * Expected values are default | config | env | cli | implied
  861. *
  862. * @param {string} key
  863. * @return {string}
  864. */
  865. getOptionValueSourceWithGlobals(key) {
  866. // global overwrites local, like optsWithGlobals
  867. let source;
  868. this._getCommandAndAncestors().forEach((cmd) => {
  869. if (cmd.getOptionValueSource(key) !== undefined) {
  870. source = cmd.getOptionValueSource(key);
  871. }
  872. });
  873. return source;
  874. }
  875. /**
  876. * Get user arguments from implied or explicit arguments.
  877. * Side-effects: set _scriptPath if args included script. Used for default program name, and subcommand searches.
  878. *
  879. * @private
  880. */
  881. _prepareUserArgs(argv, parseOptions) {
  882. if (argv !== undefined && !Array.isArray(argv)) {
  883. throw new Error('first parameter to parse must be array or undefined');
  884. }
  885. parseOptions = parseOptions || {};
  886. // auto-detect argument conventions if nothing supplied
  887. if (argv === undefined && parseOptions.from === undefined) {
  888. if (process.versions?.electron) {
  889. parseOptions.from = 'electron';
  890. }
  891. // check node specific options for scenarios where user CLI args follow executable without scriptname
  892. const execArgv = process.execArgv ?? [];
  893. if (
  894. execArgv.includes('-e') ||
  895. execArgv.includes('--eval') ||
  896. execArgv.includes('-p') ||
  897. execArgv.includes('--print')
  898. ) {
  899. parseOptions.from = 'eval'; // internal usage, not documented
  900. }
  901. }
  902. // default to using process.argv
  903. if (argv === undefined) {
  904. argv = process.argv;
  905. }
  906. this.rawArgs = argv.slice();
  907. // extract the user args and scriptPath
  908. let userArgs;
  909. switch (parseOptions.from) {
  910. case undefined:
  911. case 'node':
  912. this._scriptPath = argv[1];
  913. userArgs = argv.slice(2);
  914. break;
  915. case 'electron':
  916. // @ts-ignore: because defaultApp is an unknown property
  917. if (process.defaultApp) {
  918. this._scriptPath = argv[1];
  919. userArgs = argv.slice(2);
  920. } else {
  921. userArgs = argv.slice(1);
  922. }
  923. break;
  924. case 'user':
  925. userArgs = argv.slice(0);
  926. break;
  927. case 'eval':
  928. userArgs = argv.slice(1);
  929. break;
  930. default:
  931. throw new Error(
  932. `unexpected parse option { from: '${parseOptions.from}' }`,
  933. );
  934. }
  935. // Find default name for program from arguments.
  936. if (!this._name && this._scriptPath)
  937. this.nameFromFilename(this._scriptPath);
  938. this._name = this._name || 'program';
  939. return userArgs;
  940. }
  941. /**
  942. * Parse `argv`, setting options and invoking commands when defined.
  943. *
  944. * Use parseAsync instead of parse if any of your action handlers are async.
  945. *
  946. * Call with no parameters to parse `process.argv`. Detects Electron and special node options like `node --eval`. Easy mode!
  947. *
  948. * Or call with an array of strings to parse, and optionally where the user arguments start by specifying where the arguments are `from`:
  949. * - `'node'`: default, `argv[0]` is the application and `argv[1]` is the script being run, with user arguments after that
  950. * - `'electron'`: `argv[0]` is the application and `argv[1]` varies depending on whether the electron application is packaged
  951. * - `'user'`: just user arguments
  952. *
  953. * @example
  954. * program.parse(); // parse process.argv and auto-detect electron and special node flags
  955. * program.parse(process.argv); // assume argv[0] is app and argv[1] is script
  956. * program.parse(my-args, { from: 'user' }); // just user supplied arguments, nothing special about argv[0]
  957. *
  958. * @param {string[]} [argv] - optional, defaults to process.argv
  959. * @param {object} [parseOptions] - optionally specify style of options with from: node/user/electron
  960. * @param {string} [parseOptions.from] - where the args are from: 'node', 'user', 'electron'
  961. * @return {Command} `this` command for chaining
  962. */
  963. parse(argv, parseOptions) {
  964. this._prepareForParse();
  965. const userArgs = this._prepareUserArgs(argv, parseOptions);
  966. this._parseCommand([], userArgs);
  967. return this;
  968. }
  969. /**
  970. * Parse `argv`, setting options and invoking commands when defined.
  971. *
  972. * Call with no parameters to parse `process.argv`. Detects Electron and special node options like `node --eval`. Easy mode!
  973. *
  974. * Or call with an array of strings to parse, and optionally where the user arguments start by specifying where the arguments are `from`:
  975. * - `'node'`: default, `argv[0]` is the application and `argv[1]` is the script being run, with user arguments after that
  976. * - `'electron'`: `argv[0]` is the application and `argv[1]` varies depending on whether the electron application is packaged
  977. * - `'user'`: just user arguments
  978. *
  979. * @example
  980. * await program.parseAsync(); // parse process.argv and auto-detect electron and special node flags
  981. * await program.parseAsync(process.argv); // assume argv[0] is app and argv[1] is script
  982. * await program.parseAsync(my-args, { from: 'user' }); // just user supplied arguments, nothing special about argv[0]
  983. *
  984. * @param {string[]} [argv]
  985. * @param {object} [parseOptions]
  986. * @param {string} parseOptions.from - where the args are from: 'node', 'user', 'electron'
  987. * @return {Promise}
  988. */
  989. async parseAsync(argv, parseOptions) {
  990. this._prepareForParse();
  991. const userArgs = this._prepareUserArgs(argv, parseOptions);
  992. await this._parseCommand([], userArgs);
  993. return this;
  994. }
  995. _prepareForParse() {
  996. if (this._savedState === null) {
  997. this.saveStateBeforeParse();
  998. } else {
  999. this.restoreStateBeforeParse();
  1000. }
  1001. }
  1002. /**
  1003. * Called the first time parse is called to save state and allow a restore before subsequent calls to parse.
  1004. * Not usually called directly, but available for subclasses to save their custom state.
  1005. *
  1006. * This is called in a lazy way. Only commands used in parsing chain will have state saved.
  1007. */
  1008. saveStateBeforeParse() {
  1009. this._savedState = {
  1010. // name is stable if supplied by author, but may be unspecified for root command and deduced during parsing
  1011. _name: this._name,
  1012. // option values before parse have default values (including false for negated options)
  1013. // shallow clones
  1014. _optionValues: { ...this._optionValues },
  1015. _optionValueSources: { ...this._optionValueSources },
  1016. };
  1017. }
  1018. /**
  1019. * Restore state before parse for calls after the first.
  1020. * Not usually called directly, but available for subclasses to save their custom state.
  1021. *
  1022. * This is called in a lazy way. Only commands used in parsing chain will have state restored.
  1023. */
  1024. restoreStateBeforeParse() {
  1025. if (this._storeOptionsAsProperties)
  1026. throw new Error(`Can not call parse again when storeOptionsAsProperties is true.
  1027. - either make a new Command for each call to parse, or stop storing options as properties`);
  1028. // clear state from _prepareUserArgs
  1029. this._name = this._savedState._name;
  1030. this._scriptPath = null;
  1031. this.rawArgs = [];
  1032. // clear state from setOptionValueWithSource
  1033. this._optionValues = { ...this._savedState._optionValues };
  1034. this._optionValueSources = { ...this._savedState._optionValueSources };
  1035. // clear state from _parseCommand
  1036. this.args = [];
  1037. // clear state from _processArguments
  1038. this.processedArgs = [];
  1039. }
  1040. /**
  1041. * Throw if expected executable is missing. Add lots of help for author.
  1042. *
  1043. * @param {string} executableFile
  1044. * @param {string} executableDir
  1045. * @param {string} subcommandName
  1046. */
  1047. _checkForMissingExecutable(executableFile, executableDir, subcommandName) {
  1048. if (fs.existsSync(executableFile)) return;
  1049. const executableDirMessage = executableDir
  1050. ? `searched for local subcommand relative to directory '${executableDir}'`
  1051. : 'no directory for search for local subcommand, use .executableDir() to supply a custom directory';
  1052. const executableMissing = `'${executableFile}' does not exist
  1053. - if '${subcommandName}' is not meant to be an executable command, remove description parameter from '.command()' and use '.description()' instead
  1054. - if the default executable name is not suitable, use the executableFile option to supply a custom name or path
  1055. - ${executableDirMessage}`;
  1056. throw new Error(executableMissing);
  1057. }
  1058. /**
  1059. * Execute a sub-command executable.
  1060. *
  1061. * @private
  1062. */
  1063. _executeSubCommand(subcommand, args) {
  1064. args = args.slice();
  1065. let launchWithNode = false; // Use node for source targets so do not need to get permissions correct, and on Windows.
  1066. const sourceExt = ['.js', '.ts', '.tsx', '.mjs', '.cjs'];
  1067. function findFile(baseDir, baseName) {
  1068. // Look for specified file
  1069. const localBin = path.resolve(baseDir, baseName);
  1070. if (fs.existsSync(localBin)) return localBin;
  1071. // Stop looking if candidate already has an expected extension.
  1072. if (sourceExt.includes(path.extname(baseName))) return undefined;
  1073. // Try all the extensions.
  1074. const foundExt = sourceExt.find((ext) =>
  1075. fs.existsSync(`${localBin}${ext}`),
  1076. );
  1077. if (foundExt) return `${localBin}${foundExt}`;
  1078. return undefined;
  1079. }
  1080. // Not checking for help first. Unlikely to have mandatory and executable, and can't robustly test for help flags in external command.
  1081. this._checkForMissingMandatoryOptions();
  1082. this._checkForConflictingOptions();
  1083. // executableFile and executableDir might be full path, or just a name
  1084. let executableFile =
  1085. subcommand._executableFile || `${this._name}-${subcommand._name}`;
  1086. let executableDir = this._executableDir || '';
  1087. if (this._scriptPath) {
  1088. let resolvedScriptPath; // resolve possible symlink for installed npm binary
  1089. try {
  1090. resolvedScriptPath = fs.realpathSync(this._scriptPath);
  1091. } catch {
  1092. resolvedScriptPath = this._scriptPath;
  1093. }
  1094. executableDir = path.resolve(
  1095. path.dirname(resolvedScriptPath),
  1096. executableDir,
  1097. );
  1098. }
  1099. // Look for a local file in preference to a command in PATH.
  1100. if (executableDir) {
  1101. let localFile = findFile(executableDir, executableFile);
  1102. // Legacy search using prefix of script name instead of command name
  1103. if (!localFile && !subcommand._executableFile && this._scriptPath) {
  1104. const legacyName = path.basename(
  1105. this._scriptPath,
  1106. path.extname(this._scriptPath),
  1107. );
  1108. if (legacyName !== this._name) {
  1109. localFile = findFile(
  1110. executableDir,
  1111. `${legacyName}-${subcommand._name}`,
  1112. );
  1113. }
  1114. }
  1115. executableFile = localFile || executableFile;
  1116. }
  1117. launchWithNode = sourceExt.includes(path.extname(executableFile));
  1118. let proc;
  1119. if (process.platform !== 'win32') {
  1120. if (launchWithNode) {
  1121. args.unshift(executableFile);
  1122. // add executable arguments to spawn
  1123. args = incrementNodeInspectorPort(process.execArgv).concat(args);
  1124. proc = childProcess.spawn(process.argv[0], args, { stdio: 'inherit' });
  1125. } else {
  1126. proc = childProcess.spawn(executableFile, args, { stdio: 'inherit' });
  1127. }
  1128. } else {
  1129. this._checkForMissingExecutable(
  1130. executableFile,
  1131. executableDir,
  1132. subcommand._name,
  1133. );
  1134. args.unshift(executableFile);
  1135. // add executable arguments to spawn
  1136. args = incrementNodeInspectorPort(process.execArgv).concat(args);
  1137. proc = childProcess.spawn(process.execPath, args, { stdio: 'inherit' });
  1138. }
  1139. if (!proc.killed) {
  1140. // testing mainly to avoid leak warnings during unit tests with mocked spawn
  1141. const signals = ['SIGUSR1', 'SIGUSR2', 'SIGTERM', 'SIGINT', 'SIGHUP'];
  1142. signals.forEach((signal) => {
  1143. process.on(signal, () => {
  1144. if (proc.killed === false && proc.exitCode === null) {
  1145. // @ts-ignore because signals not typed to known strings
  1146. proc.kill(signal);
  1147. }
  1148. });
  1149. });
  1150. }
  1151. // By default terminate process when spawned process terminates.
  1152. const exitCallback = this._exitCallback;
  1153. proc.on('close', (code) => {
  1154. code = code ?? 1; // code is null if spawned process terminated due to a signal
  1155. if (!exitCallback) {
  1156. process.exit(code);
  1157. } else {
  1158. exitCallback(
  1159. new CommanderError(
  1160. code,
  1161. 'commander.executeSubCommandAsync',
  1162. '(close)',
  1163. ),
  1164. );
  1165. }
  1166. });
  1167. proc.on('error', (err) => {
  1168. // @ts-ignore: because err.code is an unknown property
  1169. if (err.code === 'ENOENT') {
  1170. this._checkForMissingExecutable(
  1171. executableFile,
  1172. executableDir,
  1173. subcommand._name,
  1174. );
  1175. // @ts-ignore: because err.code is an unknown property
  1176. } else if (err.code === 'EACCES') {
  1177. throw new Error(`'${executableFile}' not executable`);
  1178. }
  1179. if (!exitCallback) {
  1180. process.exit(1);
  1181. } else {
  1182. const wrappedError = new CommanderError(
  1183. 1,
  1184. 'commander.executeSubCommandAsync',
  1185. '(error)',
  1186. );
  1187. wrappedError.nestedError = err;
  1188. exitCallback(wrappedError);
  1189. }
  1190. });
  1191. // Store the reference to the child process
  1192. this.runningCommand = proc;
  1193. }
  1194. /**
  1195. * @private
  1196. */
  1197. _dispatchSubcommand(commandName, operands, unknown) {
  1198. const subCommand = this._findCommand(commandName);
  1199. if (!subCommand) this.help({ error: true });
  1200. subCommand._prepareForParse();
  1201. let promiseChain;
  1202. promiseChain = this._chainOrCallSubCommandHook(
  1203. promiseChain,
  1204. subCommand,
  1205. 'preSubcommand',
  1206. );
  1207. promiseChain = this._chainOrCall(promiseChain, () => {
  1208. if (subCommand._executableHandler) {
  1209. this._executeSubCommand(subCommand, operands.concat(unknown));
  1210. } else {
  1211. return subCommand._parseCommand(operands, unknown);
  1212. }
  1213. });
  1214. return promiseChain;
  1215. }
  1216. /**
  1217. * Invoke help directly if possible, or dispatch if necessary.
  1218. * e.g. help foo
  1219. *
  1220. * @private
  1221. */
  1222. _dispatchHelpCommand(subcommandName) {
  1223. if (!subcommandName) {
  1224. this.help();
  1225. }
  1226. const subCommand = this._findCommand(subcommandName);
  1227. if (subCommand && !subCommand._executableHandler) {
  1228. subCommand.help();
  1229. }
  1230. // Fallback to parsing the help flag to invoke the help.
  1231. return this._dispatchSubcommand(
  1232. subcommandName,
  1233. [],
  1234. [this._getHelpOption()?.long ?? this._getHelpOption()?.short ?? '--help'],
  1235. );
  1236. }
  1237. /**
  1238. * Check this.args against expected this.registeredArguments.
  1239. *
  1240. * @private
  1241. */
  1242. _checkNumberOfArguments() {
  1243. // too few
  1244. this.registeredArguments.forEach((arg, i) => {
  1245. if (arg.required && this.args[i] == null) {
  1246. this.missingArgument(arg.name());
  1247. }
  1248. });
  1249. // too many
  1250. if (
  1251. this.registeredArguments.length > 0 &&
  1252. this.registeredArguments[this.registeredArguments.length - 1].variadic
  1253. ) {
  1254. return;
  1255. }
  1256. if (this.args.length > this.registeredArguments.length) {
  1257. this._excessArguments(this.args);
  1258. }
  1259. }
  1260. /**
  1261. * Process this.args using this.registeredArguments and save as this.processedArgs!
  1262. *
  1263. * @private
  1264. */
  1265. _processArguments() {
  1266. const myParseArg = (argument, value, previous) => {
  1267. // Extra processing for nice error message on parsing failure.
  1268. let parsedValue = value;
  1269. if (value !== null && argument.parseArg) {
  1270. const invalidValueMessage = `error: command-argument value '${value}' is invalid for argument '${argument.name()}'.`;
  1271. parsedValue = this._callParseArg(
  1272. argument,
  1273. value,
  1274. previous,
  1275. invalidValueMessage,
  1276. );
  1277. }
  1278. return parsedValue;
  1279. };
  1280. this._checkNumberOfArguments();
  1281. const processedArgs = [];
  1282. this.registeredArguments.forEach((declaredArg, index) => {
  1283. let value = declaredArg.defaultValue;
  1284. if (declaredArg.variadic) {
  1285. // Collect together remaining arguments for passing together as an array.
  1286. if (index < this.args.length) {
  1287. value = this.args.slice(index);
  1288. if (declaredArg.parseArg) {
  1289. value = value.reduce((processed, v) => {
  1290. return myParseArg(declaredArg, v, processed);
  1291. }, declaredArg.defaultValue);
  1292. }
  1293. } else if (value === undefined) {
  1294. value = [];
  1295. }
  1296. } else if (index < this.args.length) {
  1297. value = this.args[index];
  1298. if (declaredArg.parseArg) {
  1299. value = myParseArg(declaredArg, value, declaredArg.defaultValue);
  1300. }
  1301. }
  1302. processedArgs[index] = value;
  1303. });
  1304. this.processedArgs = processedArgs;
  1305. }
  1306. /**
  1307. * Once we have a promise we chain, but call synchronously until then.
  1308. *
  1309. * @param {(Promise|undefined)} promise
  1310. * @param {Function} fn
  1311. * @return {(Promise|undefined)}
  1312. * @private
  1313. */
  1314. _chainOrCall(promise, fn) {
  1315. // thenable
  1316. if (promise && promise.then && typeof promise.then === 'function') {
  1317. // already have a promise, chain callback
  1318. return promise.then(() => fn());
  1319. }
  1320. // callback might return a promise
  1321. return fn();
  1322. }
  1323. /**
  1324. *
  1325. * @param {(Promise|undefined)} promise
  1326. * @param {string} event
  1327. * @return {(Promise|undefined)}
  1328. * @private
  1329. */
  1330. _chainOrCallHooks(promise, event) {
  1331. let result = promise;
  1332. const hooks = [];
  1333. this._getCommandAndAncestors()
  1334. .reverse()
  1335. .filter((cmd) => cmd._lifeCycleHooks[event] !== undefined)
  1336. .forEach((hookedCommand) => {
  1337. hookedCommand._lifeCycleHooks[event].forEach((callback) => {
  1338. hooks.push({ hookedCommand, callback });
  1339. });
  1340. });
  1341. if (event === 'postAction') {
  1342. hooks.reverse();
  1343. }
  1344. hooks.forEach((hookDetail) => {
  1345. result = this._chainOrCall(result, () => {
  1346. return hookDetail.callback(hookDetail.hookedCommand, this);
  1347. });
  1348. });
  1349. return result;
  1350. }
  1351. /**
  1352. *
  1353. * @param {(Promise|undefined)} promise
  1354. * @param {Command} subCommand
  1355. * @param {string} event
  1356. * @return {(Promise|undefined)}
  1357. * @private
  1358. */
  1359. _chainOrCallSubCommandHook(promise, subCommand, event) {
  1360. let result = promise;
  1361. if (this._lifeCycleHooks[event] !== undefined) {
  1362. this._lifeCycleHooks[event].forEach((hook) => {
  1363. result = this._chainOrCall(result, () => {
  1364. return hook(this, subCommand);
  1365. });
  1366. });
  1367. }
  1368. return result;
  1369. }
  1370. /**
  1371. * Process arguments in context of this command.
  1372. * Returns action result, in case it is a promise.
  1373. *
  1374. * @private
  1375. */
  1376. _parseCommand(operands, unknown) {
  1377. const parsed = this.parseOptions(unknown);
  1378. this._parseOptionsEnv(); // after cli, so parseArg not called on both cli and env
  1379. this._parseOptionsImplied();
  1380. operands = operands.concat(parsed.operands);
  1381. unknown = parsed.unknown;
  1382. this.args = operands.concat(unknown);
  1383. if (operands && this._findCommand(operands[0])) {
  1384. return this._dispatchSubcommand(operands[0], operands.slice(1), unknown);
  1385. }
  1386. if (
  1387. this._getHelpCommand() &&
  1388. operands[0] === this._getHelpCommand().name()
  1389. ) {
  1390. return this._dispatchHelpCommand(operands[1]);
  1391. }
  1392. if (this._defaultCommandName) {
  1393. this._outputHelpIfRequested(unknown); // Run the help for default command from parent rather than passing to default command
  1394. return this._dispatchSubcommand(
  1395. this._defaultCommandName,
  1396. operands,
  1397. unknown,
  1398. );
  1399. }
  1400. if (
  1401. this.commands.length &&
  1402. this.args.length === 0 &&
  1403. !this._actionHandler &&
  1404. !this._defaultCommandName
  1405. ) {
  1406. // probably missing subcommand and no handler, user needs help (and exit)
  1407. this.help({ error: true });
  1408. }
  1409. this._outputHelpIfRequested(parsed.unknown);
  1410. this._checkForMissingMandatoryOptions();
  1411. this._checkForConflictingOptions();
  1412. // We do not always call this check to avoid masking a "better" error, like unknown command.
  1413. const checkForUnknownOptions = () => {
  1414. if (parsed.unknown.length > 0) {
  1415. this.unknownOption(parsed.unknown[0]);
  1416. }
  1417. };
  1418. const commandEvent = `command:${this.name()}`;
  1419. if (this._actionHandler) {
  1420. checkForUnknownOptions();
  1421. this._processArguments();
  1422. let promiseChain;
  1423. promiseChain = this._chainOrCallHooks(promiseChain, 'preAction');
  1424. promiseChain = this._chainOrCall(promiseChain, () =>
  1425. this._actionHandler(this.processedArgs),
  1426. );
  1427. if (this.parent) {
  1428. promiseChain = this._chainOrCall(promiseChain, () => {
  1429. this.parent.emit(commandEvent, operands, unknown); // legacy
  1430. });
  1431. }
  1432. promiseChain = this._chainOrCallHooks(promiseChain, 'postAction');
  1433. return promiseChain;
  1434. }
  1435. if (this.parent && this.parent.listenerCount(commandEvent)) {
  1436. checkForUnknownOptions();
  1437. this._processArguments();
  1438. this.parent.emit(commandEvent, operands, unknown); // legacy
  1439. } else if (operands.length) {
  1440. if (this._findCommand('*')) {
  1441. // legacy default command
  1442. return this._dispatchSubcommand('*', operands, unknown);
  1443. }
  1444. if (this.listenerCount('command:*')) {
  1445. // skip option check, emit event for possible misspelling suggestion
  1446. this.emit('command:*', operands, unknown);
  1447. } else if (this.commands.length) {
  1448. this.unknownCommand();
  1449. } else {
  1450. checkForUnknownOptions();
  1451. this._processArguments();
  1452. }
  1453. } else if (this.commands.length) {
  1454. checkForUnknownOptions();
  1455. // This command has subcommands and nothing hooked up at this level, so display help (and exit).
  1456. this.help({ error: true });
  1457. } else {
  1458. checkForUnknownOptions();
  1459. this._processArguments();
  1460. // fall through for caller to handle after calling .parse()
  1461. }
  1462. }
  1463. /**
  1464. * Find matching command.
  1465. *
  1466. * @private
  1467. * @return {Command | undefined}
  1468. */
  1469. _findCommand(name) {
  1470. if (!name) return undefined;
  1471. return this.commands.find(
  1472. (cmd) => cmd._name === name || cmd._aliases.includes(name),
  1473. );
  1474. }
  1475. /**
  1476. * Return an option matching `arg` if any.
  1477. *
  1478. * @param {string} arg
  1479. * @return {Option}
  1480. * @package
  1481. */
  1482. _findOption(arg) {
  1483. return this.options.find((option) => option.is(arg));
  1484. }
  1485. /**
  1486. * Display an error message if a mandatory option does not have a value.
  1487. * Called after checking for help flags in leaf subcommand.
  1488. *
  1489. * @private
  1490. */
  1491. _checkForMissingMandatoryOptions() {
  1492. // Walk up hierarchy so can call in subcommand after checking for displaying help.
  1493. this._getCommandAndAncestors().forEach((cmd) => {
  1494. cmd.options.forEach((anOption) => {
  1495. if (
  1496. anOption.mandatory &&
  1497. cmd.getOptionValue(anOption.attributeName()) === undefined
  1498. ) {
  1499. cmd.missingMandatoryOptionValue(anOption);
  1500. }
  1501. });
  1502. });
  1503. }
  1504. /**
  1505. * Display an error message if conflicting options are used together in this.
  1506. *
  1507. * @private
  1508. */
  1509. _checkForConflictingLocalOptions() {
  1510. const definedNonDefaultOptions = this.options.filter((option) => {
  1511. const optionKey = option.attributeName();
  1512. if (this.getOptionValue(optionKey) === undefined) {
  1513. return false;
  1514. }
  1515. return this.getOptionValueSource(optionKey) !== 'default';
  1516. });
  1517. const optionsWithConflicting = definedNonDefaultOptions.filter(
  1518. (option) => option.conflictsWith.length > 0,
  1519. );
  1520. optionsWithConflicting.forEach((option) => {
  1521. const conflictingAndDefined = definedNonDefaultOptions.find((defined) =>
  1522. option.conflictsWith.includes(defined.attributeName()),
  1523. );
  1524. if (conflictingAndDefined) {
  1525. this._conflictingOption(option, conflictingAndDefined);
  1526. }
  1527. });
  1528. }
  1529. /**
  1530. * Display an error message if conflicting options are used together.
  1531. * Called after checking for help flags in leaf subcommand.
  1532. *
  1533. * @private
  1534. */
  1535. _checkForConflictingOptions() {
  1536. // Walk up hierarchy so can call in subcommand after checking for displaying help.
  1537. this._getCommandAndAncestors().forEach((cmd) => {
  1538. cmd._checkForConflictingLocalOptions();
  1539. });
  1540. }
  1541. /**
  1542. * Parse options from `argv` removing known options,
  1543. * and return argv split into operands and unknown arguments.
  1544. *
  1545. * Side effects: modifies command by storing options. Does not reset state if called again.
  1546. *
  1547. * Examples:
  1548. *
  1549. * argv => operands, unknown
  1550. * --known kkk op => [op], []
  1551. * op --known kkk => [op], []
  1552. * sub --unknown uuu op => [sub], [--unknown uuu op]
  1553. * sub -- --unknown uuu op => [sub --unknown uuu op], []
  1554. *
  1555. * @param {string[]} argv
  1556. * @return {{operands: string[], unknown: string[]}}
  1557. */
  1558. parseOptions(argv) {
  1559. const operands = []; // operands, not options or values
  1560. const unknown = []; // first unknown option and remaining unknown args
  1561. let dest = operands;
  1562. const args = argv.slice();
  1563. function maybeOption(arg) {
  1564. return arg.length > 1 && arg[0] === '-';
  1565. }
  1566. // parse options
  1567. let activeVariadicOption = null;
  1568. while (args.length) {
  1569. const arg = args.shift();
  1570. // literal
  1571. if (arg === '--') {
  1572. if (dest === unknown) dest.push(arg);
  1573. dest.push(...args);
  1574. break;
  1575. }
  1576. if (activeVariadicOption && !maybeOption(arg)) {
  1577. this.emit(`option:${activeVariadicOption.name()}`, arg);
  1578. continue;
  1579. }
  1580. activeVariadicOption = null;
  1581. if (maybeOption(arg)) {
  1582. const option = this._findOption(arg);
  1583. // recognised option, call listener to assign value with possible custom processing
  1584. if (option) {
  1585. if (option.required) {
  1586. const value = args.shift();
  1587. if (value === undefined) this.optionMissingArgument(option);
  1588. this.emit(`option:${option.name()}`, value);
  1589. } else if (option.optional) {
  1590. let value = null;
  1591. // historical behaviour is optional value is following arg unless an option
  1592. if (args.length > 0 && !maybeOption(args[0])) {
  1593. value = args.shift();
  1594. }
  1595. this.emit(`option:${option.name()}`, value);
  1596. } else {
  1597. // boolean flag
  1598. this.emit(`option:${option.name()}`);
  1599. }
  1600. activeVariadicOption = option.variadic ? option : null;
  1601. continue;
  1602. }
  1603. }
  1604. // Look for combo options following single dash, eat first one if known.
  1605. if (arg.length > 2 && arg[0] === '-' && arg[1] !== '-') {
  1606. const option = this._findOption(`-${arg[1]}`);
  1607. if (option) {
  1608. if (
  1609. option.required ||
  1610. (option.optional && this._combineFlagAndOptionalValue)
  1611. ) {
  1612. // option with value following in same argument
  1613. this.emit(`option:${option.name()}`, arg.slice(2));
  1614. } else {
  1615. // boolean option, emit and put back remainder of arg for further processing
  1616. this.emit(`option:${option.name()}`);
  1617. args.unshift(`-${arg.slice(2)}`);
  1618. }
  1619. continue;
  1620. }
  1621. }
  1622. // Look for known long flag with value, like --foo=bar
  1623. if (/^--[^=]+=/.test(arg)) {
  1624. const index = arg.indexOf('=');
  1625. const option = this._findOption(arg.slice(0, index));
  1626. if (option && (option.required || option.optional)) {
  1627. this.emit(`option:${option.name()}`, arg.slice(index + 1));
  1628. continue;
  1629. }
  1630. }
  1631. // Not a recognised option by this command.
  1632. // Might be a command-argument, or subcommand option, or unknown option, or help command or option.
  1633. // An unknown option means further arguments also classified as unknown so can be reprocessed by subcommands.
  1634. if (maybeOption(arg)) {
  1635. dest = unknown;
  1636. }
  1637. // If using positionalOptions, stop processing our options at subcommand.
  1638. if (
  1639. (this._enablePositionalOptions || this._passThroughOptions) &&
  1640. operands.length === 0 &&
  1641. unknown.length === 0
  1642. ) {
  1643. if (this._findCommand(arg)) {
  1644. operands.push(arg);
  1645. if (args.length > 0) unknown.push(...args);
  1646. break;
  1647. } else if (
  1648. this._getHelpCommand() &&
  1649. arg === this._getHelpCommand().name()
  1650. ) {
  1651. operands.push(arg);
  1652. if (args.length > 0) operands.push(...args);
  1653. break;
  1654. } else if (this._defaultCommandName) {
  1655. unknown.push(arg);
  1656. if (args.length > 0) unknown.push(...args);
  1657. break;
  1658. }
  1659. }
  1660. // If using passThroughOptions, stop processing options at first command-argument.
  1661. if (this._passThroughOptions) {
  1662. dest.push(arg);
  1663. if (args.length > 0) dest.push(...args);
  1664. break;
  1665. }
  1666. // add arg
  1667. dest.push(arg);
  1668. }
  1669. return { operands, unknown };
  1670. }
  1671. /**
  1672. * Return an object containing local option values as key-value pairs.
  1673. *
  1674. * @return {object}
  1675. */
  1676. opts() {
  1677. if (this._storeOptionsAsProperties) {
  1678. // Preserve original behaviour so backwards compatible when still using properties
  1679. const result = {};
  1680. const len = this.options.length;
  1681. for (let i = 0; i < len; i++) {
  1682. const key = this.options[i].attributeName();
  1683. result[key] =
  1684. key === this._versionOptionName ? this._version : this[key];
  1685. }
  1686. return result;
  1687. }
  1688. return this._optionValues;
  1689. }
  1690. /**
  1691. * Return an object containing merged local and global option values as key-value pairs.
  1692. *
  1693. * @return {object}
  1694. */
  1695. optsWithGlobals() {
  1696. // globals overwrite locals
  1697. return this._getCommandAndAncestors().reduce(
  1698. (combinedOptions, cmd) => Object.assign(combinedOptions, cmd.opts()),
  1699. {},
  1700. );
  1701. }
  1702. /**
  1703. * Display error message and exit (or call exitOverride).
  1704. *
  1705. * @param {string} message
  1706. * @param {object} [errorOptions]
  1707. * @param {string} [errorOptions.code] - an id string representing the error
  1708. * @param {number} [errorOptions.exitCode] - used with process.exit
  1709. */
  1710. error(message, errorOptions) {
  1711. // output handling
  1712. this._outputConfiguration.outputError(
  1713. `${message}\n`,
  1714. this._outputConfiguration.writeErr,
  1715. );
  1716. if (typeof this._showHelpAfterError === 'string') {
  1717. this._outputConfiguration.writeErr(`${this._showHelpAfterError}\n`);
  1718. } else if (this._showHelpAfterError) {
  1719. this._outputConfiguration.writeErr('\n');
  1720. this.outputHelp({ error: true });
  1721. }
  1722. // exit handling
  1723. const config = errorOptions || {};
  1724. const exitCode = config.exitCode || 1;
  1725. const code = config.code || 'commander.error';
  1726. this._exit(exitCode, code, message);
  1727. }
  1728. /**
  1729. * Apply any option related environment variables, if option does
  1730. * not have a value from cli or client code.
  1731. *
  1732. * @private
  1733. */
  1734. _parseOptionsEnv() {
  1735. this.options.forEach((option) => {
  1736. if (option.envVar && option.envVar in process.env) {
  1737. const optionKey = option.attributeName();
  1738. // Priority check. Do not overwrite cli or options from unknown source (client-code).
  1739. if (
  1740. this.getOptionValue(optionKey) === undefined ||
  1741. ['default', 'config', 'env'].includes(
  1742. this.getOptionValueSource(optionKey),
  1743. )
  1744. ) {
  1745. if (option.required || option.optional) {
  1746. // option can take a value
  1747. // keep very simple, optional always takes value
  1748. this.emit(`optionEnv:${option.name()}`, process.env[option.envVar]);
  1749. } else {
  1750. // boolean
  1751. // keep very simple, only care that envVar defined and not the value
  1752. this.emit(`optionEnv:${option.name()}`);
  1753. }
  1754. }
  1755. }
  1756. });
  1757. }
  1758. /**
  1759. * Apply any implied option values, if option is undefined or default value.
  1760. *
  1761. * @private
  1762. */
  1763. _parseOptionsImplied() {
  1764. const dualHelper = new DualOptions(this.options);
  1765. const hasCustomOptionValue = (optionKey) => {
  1766. return (
  1767. this.getOptionValue(optionKey) !== undefined &&
  1768. !['default', 'implied'].includes(this.getOptionValueSource(optionKey))
  1769. );
  1770. };
  1771. this.options
  1772. .filter(
  1773. (option) =>
  1774. option.implied !== undefined &&
  1775. hasCustomOptionValue(option.attributeName()) &&
  1776. dualHelper.valueFromOption(
  1777. this.getOptionValue(option.attributeName()),
  1778. option,
  1779. ),
  1780. )
  1781. .forEach((option) => {
  1782. Object.keys(option.implied)
  1783. .filter((impliedKey) => !hasCustomOptionValue(impliedKey))
  1784. .forEach((impliedKey) => {
  1785. this.setOptionValueWithSource(
  1786. impliedKey,
  1787. option.implied[impliedKey],
  1788. 'implied',
  1789. );
  1790. });
  1791. });
  1792. }
  1793. /**
  1794. * Argument `name` is missing.
  1795. *
  1796. * @param {string} name
  1797. * @private
  1798. */
  1799. missingArgument(name) {
  1800. const message = `error: missing required argument '${name}'`;
  1801. this.error(message, { code: 'commander.missingArgument' });
  1802. }
  1803. /**
  1804. * `Option` is missing an argument.
  1805. *
  1806. * @param {Option} option
  1807. * @private
  1808. */
  1809. optionMissingArgument(option) {
  1810. const message = `error: option '${option.flags}' argument missing`;
  1811. this.error(message, { code: 'commander.optionMissingArgument' });
  1812. }
  1813. /**
  1814. * `Option` does not have a value, and is a mandatory option.
  1815. *
  1816. * @param {Option} option
  1817. * @private
  1818. */
  1819. missingMandatoryOptionValue(option) {
  1820. const message = `error: required option '${option.flags}' not specified`;
  1821. this.error(message, { code: 'commander.missingMandatoryOptionValue' });
  1822. }
  1823. /**
  1824. * `Option` conflicts with another option.
  1825. *
  1826. * @param {Option} option
  1827. * @param {Option} conflictingOption
  1828. * @private
  1829. */
  1830. _conflictingOption(option, conflictingOption) {
  1831. // The calling code does not know whether a negated option is the source of the
  1832. // value, so do some work to take an educated guess.
  1833. const findBestOptionFromValue = (option) => {
  1834. const optionKey = option.attributeName();
  1835. const optionValue = this.getOptionValue(optionKey);
  1836. const negativeOption = this.options.find(
  1837. (target) => target.negate && optionKey === target.attributeName(),
  1838. );
  1839. const positiveOption = this.options.find(
  1840. (target) => !target.negate && optionKey === target.attributeName(),
  1841. );
  1842. if (
  1843. negativeOption &&
  1844. ((negativeOption.presetArg === undefined && optionValue === false) ||
  1845. (negativeOption.presetArg !== undefined &&
  1846. optionValue === negativeOption.presetArg))
  1847. ) {
  1848. return negativeOption;
  1849. }
  1850. return positiveOption || option;
  1851. };
  1852. const getErrorMessage = (option) => {
  1853. const bestOption = findBestOptionFromValue(option);
  1854. const optionKey = bestOption.attributeName();
  1855. const source = this.getOptionValueSource(optionKey);
  1856. if (source === 'env') {
  1857. return `environment variable '${bestOption.envVar}'`;
  1858. }
  1859. return `option '${bestOption.flags}'`;
  1860. };
  1861. const message = `error: ${getErrorMessage(option)} cannot be used with ${getErrorMessage(conflictingOption)}`;
  1862. this.error(message, { code: 'commander.conflictingOption' });
  1863. }
  1864. /**
  1865. * Unknown option `flag`.
  1866. *
  1867. * @param {string} flag
  1868. * @private
  1869. */
  1870. unknownOption(flag) {
  1871. if (this._allowUnknownOption) return;
  1872. let suggestion = '';
  1873. if (flag.startsWith('--') && this._showSuggestionAfterError) {
  1874. // Looping to pick up the global options too
  1875. let candidateFlags = [];
  1876. // eslint-disable-next-line @typescript-eslint/no-this-alias
  1877. let command = this;
  1878. do {
  1879. const moreFlags = command
  1880. .createHelp()
  1881. .visibleOptions(command)
  1882. .filter((option) => option.long)
  1883. .map((option) => option.long);
  1884. candidateFlags = candidateFlags.concat(moreFlags);
  1885. command = command.parent;
  1886. } while (command && !command._enablePositionalOptions);
  1887. suggestion = suggestSimilar(flag, candidateFlags);
  1888. }
  1889. const message = `error: unknown option '${flag}'${suggestion}`;
  1890. this.error(message, { code: 'commander.unknownOption' });
  1891. }
  1892. /**
  1893. * Excess arguments, more than expected.
  1894. *
  1895. * @param {string[]} receivedArgs
  1896. * @private
  1897. */
  1898. _excessArguments(receivedArgs) {
  1899. if (this._allowExcessArguments) return;
  1900. const expected = this.registeredArguments.length;
  1901. const s = expected === 1 ? '' : 's';
  1902. const forSubcommand = this.parent ? ` for '${this.name()}'` : '';
  1903. const message = `error: too many arguments${forSubcommand}. Expected ${expected} argument${s} but got ${receivedArgs.length}.`;
  1904. this.error(message, { code: 'commander.excessArguments' });
  1905. }
  1906. /**
  1907. * Unknown command.
  1908. *
  1909. * @private
  1910. */
  1911. unknownCommand() {
  1912. const unknownName = this.args[0];
  1913. let suggestion = '';
  1914. if (this._showSuggestionAfterError) {
  1915. const candidateNames = [];
  1916. this.createHelp()
  1917. .visibleCommands(this)
  1918. .forEach((command) => {
  1919. candidateNames.push(command.name());
  1920. // just visible alias
  1921. if (command.alias()) candidateNames.push(command.alias());
  1922. });
  1923. suggestion = suggestSimilar(unknownName, candidateNames);
  1924. }
  1925. const message = `error: unknown command '${unknownName}'${suggestion}`;
  1926. this.error(message, { code: 'commander.unknownCommand' });
  1927. }
  1928. /**
  1929. * Get or set the program version.
  1930. *
  1931. * This method auto-registers the "-V, --version" option which will print the version number.
  1932. *
  1933. * You can optionally supply the flags and description to override the defaults.
  1934. *
  1935. * @param {string} [str]
  1936. * @param {string} [flags]
  1937. * @param {string} [description]
  1938. * @return {(this | string | undefined)} `this` command for chaining, or version string if no arguments
  1939. */
  1940. version(str, flags, description) {
  1941. if (str === undefined) return this._version;
  1942. this._version = str;
  1943. flags = flags || '-V, --version';
  1944. description = description || 'output the version number';
  1945. const versionOption = this.createOption(flags, description);
  1946. this._versionOptionName = versionOption.attributeName();
  1947. this._registerOption(versionOption);
  1948. this.on('option:' + versionOption.name(), () => {
  1949. this._outputConfiguration.writeOut(`${str}\n`);
  1950. this._exit(0, 'commander.version', str);
  1951. });
  1952. return this;
  1953. }
  1954. /**
  1955. * Set the description.
  1956. *
  1957. * @param {string} [str]
  1958. * @param {object} [argsDescription]
  1959. * @return {(string|Command)}
  1960. */
  1961. description(str, argsDescription) {
  1962. if (str === undefined && argsDescription === undefined)
  1963. return this._description;
  1964. this._description = str;
  1965. if (argsDescription) {
  1966. this._argsDescription = argsDescription;
  1967. }
  1968. return this;
  1969. }
  1970. /**
  1971. * Set the summary. Used when listed as subcommand of parent.
  1972. *
  1973. * @param {string} [str]
  1974. * @return {(string|Command)}
  1975. */
  1976. summary(str) {
  1977. if (str === undefined) return this._summary;
  1978. this._summary = str;
  1979. return this;
  1980. }
  1981. /**
  1982. * Set an alias for the command.
  1983. *
  1984. * You may call more than once to add multiple aliases. Only the first alias is shown in the auto-generated help.
  1985. *
  1986. * @param {string} [alias]
  1987. * @return {(string|Command)}
  1988. */
  1989. alias(alias) {
  1990. if (alias === undefined) return this._aliases[0]; // just return first, for backwards compatibility
  1991. /** @type {Command} */
  1992. // eslint-disable-next-line @typescript-eslint/no-this-alias
  1993. let command = this;
  1994. if (
  1995. this.commands.length !== 0 &&
  1996. this.commands[this.commands.length - 1]._executableHandler
  1997. ) {
  1998. // assume adding alias for last added executable subcommand, rather than this
  1999. command = this.commands[this.commands.length - 1];
  2000. }
  2001. if (alias === command._name)
  2002. throw new Error("Command alias can't be the same as its name");
  2003. const matchingCommand = this.parent?._findCommand(alias);
  2004. if (matchingCommand) {
  2005. // c.f. _registerCommand
  2006. const existingCmd = [matchingCommand.name()]
  2007. .concat(matchingCommand.aliases())
  2008. .join('|');
  2009. throw new Error(
  2010. `cannot add alias '${alias}' to command '${this.name()}' as already have command '${existingCmd}'`,
  2011. );
  2012. }
  2013. command._aliases.push(alias);
  2014. return this;
  2015. }
  2016. /**
  2017. * Set aliases for the command.
  2018. *
  2019. * Only the first alias is shown in the auto-generated help.
  2020. *
  2021. * @param {string[]} [aliases]
  2022. * @return {(string[]|Command)}
  2023. */
  2024. aliases(aliases) {
  2025. // Getter for the array of aliases is the main reason for having aliases() in addition to alias().
  2026. if (aliases === undefined) return this._aliases;
  2027. aliases.forEach((alias) => this.alias(alias));
  2028. return this;
  2029. }
  2030. /**
  2031. * Set / get the command usage `str`.
  2032. *
  2033. * @param {string} [str]
  2034. * @return {(string|Command)}
  2035. */
  2036. usage(str) {
  2037. if (str === undefined) {
  2038. if (this._usage) return this._usage;
  2039. const args = this.registeredArguments.map((arg) => {
  2040. return humanReadableArgName(arg);
  2041. });
  2042. return []
  2043. .concat(
  2044. this.options.length || this._helpOption !== null ? '[options]' : [],
  2045. this.commands.length ? '[command]' : [],
  2046. this.registeredArguments.length ? args : [],
  2047. )
  2048. .join(' ');
  2049. }
  2050. this._usage = str;
  2051. return this;
  2052. }
  2053. /**
  2054. * Get or set the name of the command.
  2055. *
  2056. * @param {string} [str]
  2057. * @return {(string|Command)}
  2058. */
  2059. name(str) {
  2060. if (str === undefined) return this._name;
  2061. this._name = str;
  2062. return this;
  2063. }
  2064. /**
  2065. * Set the name of the command from script filename, such as process.argv[1],
  2066. * or require.main.filename, or __filename.
  2067. *
  2068. * (Used internally and public although not documented in README.)
  2069. *
  2070. * @example
  2071. * program.nameFromFilename(require.main.filename);
  2072. *
  2073. * @param {string} filename
  2074. * @return {Command}
  2075. */
  2076. nameFromFilename(filename) {
  2077. this._name = path.basename(filename, path.extname(filename));
  2078. return this;
  2079. }
  2080. /**
  2081. * Get or set the directory for searching for executable subcommands of this command.
  2082. *
  2083. * @example
  2084. * program.executableDir(__dirname);
  2085. * // or
  2086. * program.executableDir('subcommands');
  2087. *
  2088. * @param {string} [path]
  2089. * @return {(string|null|Command)}
  2090. */
  2091. executableDir(path) {
  2092. if (path === undefined) return this._executableDir;
  2093. this._executableDir = path;
  2094. return this;
  2095. }
  2096. /**
  2097. * Return program help documentation.
  2098. *
  2099. * @param {{ error: boolean }} [contextOptions] - pass {error:true} to wrap for stderr instead of stdout
  2100. * @return {string}
  2101. */
  2102. helpInformation(contextOptions) {
  2103. const helper = this.createHelp();
  2104. const context = this._getOutputContext(contextOptions);
  2105. helper.prepareContext({
  2106. error: context.error,
  2107. helpWidth: context.helpWidth,
  2108. outputHasColors: context.hasColors,
  2109. });
  2110. const text = helper.formatHelp(this, helper);
  2111. if (context.hasColors) return text;
  2112. return this._outputConfiguration.stripColor(text);
  2113. }
  2114. /**
  2115. * @typedef HelpContext
  2116. * @type {object}
  2117. * @property {boolean} error
  2118. * @property {number} helpWidth
  2119. * @property {boolean} hasColors
  2120. * @property {function} write - includes stripColor if needed
  2121. *
  2122. * @returns {HelpContext}
  2123. * @private
  2124. */
  2125. _getOutputContext(contextOptions) {
  2126. contextOptions = contextOptions || {};
  2127. const error = !!contextOptions.error;
  2128. let baseWrite;
  2129. let hasColors;
  2130. let helpWidth;
  2131. if (error) {
  2132. baseWrite = (str) => this._outputConfiguration.writeErr(str);
  2133. hasColors = this._outputConfiguration.getErrHasColors();
  2134. helpWidth = this._outputConfiguration.getErrHelpWidth();
  2135. } else {
  2136. baseWrite = (str) => this._outputConfiguration.writeOut(str);
  2137. hasColors = this._outputConfiguration.getOutHasColors();
  2138. helpWidth = this._outputConfiguration.getOutHelpWidth();
  2139. }
  2140. const write = (str) => {
  2141. if (!hasColors) str = this._outputConfiguration.stripColor(str);
  2142. return baseWrite(str);
  2143. };
  2144. return { error, write, hasColors, helpWidth };
  2145. }
  2146. /**
  2147. * Output help information for this command.
  2148. *
  2149. * Outputs built-in help, and custom text added using `.addHelpText()`.
  2150. *
  2151. * @param {{ error: boolean } | Function} [contextOptions] - pass {error:true} to write to stderr instead of stdout
  2152. */
  2153. outputHelp(contextOptions) {
  2154. let deprecatedCallback;
  2155. if (typeof contextOptions === 'function') {
  2156. deprecatedCallback = contextOptions;
  2157. contextOptions = undefined;
  2158. }
  2159. const outputContext = this._getOutputContext(contextOptions);
  2160. /** @type {HelpTextEventContext} */
  2161. const eventContext = {
  2162. error: outputContext.error,
  2163. write: outputContext.write,
  2164. command: this,
  2165. };
  2166. this._getCommandAndAncestors()
  2167. .reverse()
  2168. .forEach((command) => command.emit('beforeAllHelp', eventContext));
  2169. this.emit('beforeHelp', eventContext);
  2170. let helpInformation = this.helpInformation({ error: outputContext.error });
  2171. if (deprecatedCallback) {
  2172. helpInformation = deprecatedCallback(helpInformation);
  2173. if (
  2174. typeof helpInformation !== 'string' &&
  2175. !Buffer.isBuffer(helpInformation)
  2176. ) {
  2177. throw new Error('outputHelp callback must return a string or a Buffer');
  2178. }
  2179. }
  2180. outputContext.write(helpInformation);
  2181. if (this._getHelpOption()?.long) {
  2182. this.emit(this._getHelpOption().long); // deprecated
  2183. }
  2184. this.emit('afterHelp', eventContext);
  2185. this._getCommandAndAncestors().forEach((command) =>
  2186. command.emit('afterAllHelp', eventContext),
  2187. );
  2188. }
  2189. /**
  2190. * You can pass in flags and a description to customise the built-in help option.
  2191. * Pass in false to disable the built-in help option.
  2192. *
  2193. * @example
  2194. * program.helpOption('-?, --help' 'show help'); // customise
  2195. * program.helpOption(false); // disable
  2196. *
  2197. * @param {(string | boolean)} flags
  2198. * @param {string} [description]
  2199. * @return {Command} `this` command for chaining
  2200. */
  2201. helpOption(flags, description) {
  2202. // Support disabling built-in help option.
  2203. if (typeof flags === 'boolean') {
  2204. // true is not an expected value. Do something sensible but no unit-test.
  2205. // istanbul ignore if
  2206. if (flags) {
  2207. this._helpOption = this._helpOption ?? undefined; // preserve existing option
  2208. } else {
  2209. this._helpOption = null; // disable
  2210. }
  2211. return this;
  2212. }
  2213. // Customise flags and description.
  2214. flags = flags ?? '-h, --help';
  2215. description = description ?? 'display help for command';
  2216. this._helpOption = this.createOption(flags, description);
  2217. return this;
  2218. }
  2219. /**
  2220. * Lazy create help option.
  2221. * Returns null if has been disabled with .helpOption(false).
  2222. *
  2223. * @returns {(Option | null)} the help option
  2224. * @package
  2225. */
  2226. _getHelpOption() {
  2227. // Lazy create help option on demand.
  2228. if (this._helpOption === undefined) {
  2229. this.helpOption(undefined, undefined);
  2230. }
  2231. return this._helpOption;
  2232. }
  2233. /**
  2234. * Supply your own option to use for the built-in help option.
  2235. * This is an alternative to using helpOption() to customise the flags and description etc.
  2236. *
  2237. * @param {Option} option
  2238. * @return {Command} `this` command for chaining
  2239. */
  2240. addHelpOption(option) {
  2241. this._helpOption = option;
  2242. return this;
  2243. }
  2244. /**
  2245. * Output help information and exit.
  2246. *
  2247. * Outputs built-in help, and custom text added using `.addHelpText()`.
  2248. *
  2249. * @param {{ error: boolean }} [contextOptions] - pass {error:true} to write to stderr instead of stdout
  2250. */
  2251. help(contextOptions) {
  2252. this.outputHelp(contextOptions);
  2253. let exitCode = Number(process.exitCode ?? 0); // process.exitCode does allow a string or an integer, but we prefer just a number
  2254. if (
  2255. exitCode === 0 &&
  2256. contextOptions &&
  2257. typeof contextOptions !== 'function' &&
  2258. contextOptions.error
  2259. ) {
  2260. exitCode = 1;
  2261. }
  2262. // message: do not have all displayed text available so only passing placeholder.
  2263. this._exit(exitCode, 'commander.help', '(outputHelp)');
  2264. }
  2265. /**
  2266. * // Do a little typing to coordinate emit and listener for the help text events.
  2267. * @typedef HelpTextEventContext
  2268. * @type {object}
  2269. * @property {boolean} error
  2270. * @property {Command} command
  2271. * @property {function} write
  2272. */
  2273. /**
  2274. * Add additional text to be displayed with the built-in help.
  2275. *
  2276. * Position is 'before' or 'after' to affect just this command,
  2277. * and 'beforeAll' or 'afterAll' to affect this command and all its subcommands.
  2278. *
  2279. * @param {string} position - before or after built-in help
  2280. * @param {(string | Function)} text - string to add, or a function returning a string
  2281. * @return {Command} `this` command for chaining
  2282. */
  2283. addHelpText(position, text) {
  2284. const allowedValues = ['beforeAll', 'before', 'after', 'afterAll'];
  2285. if (!allowedValues.includes(position)) {
  2286. throw new Error(`Unexpected value for position to addHelpText.
  2287. Expecting one of '${allowedValues.join("', '")}'`);
  2288. }
  2289. const helpEvent = `${position}Help`;
  2290. this.on(helpEvent, (/** @type {HelpTextEventContext} */ context) => {
  2291. let helpStr;
  2292. if (typeof text === 'function') {
  2293. helpStr = text({ error: context.error, command: context.command });
  2294. } else {
  2295. helpStr = text;
  2296. }
  2297. // Ignore falsy value when nothing to output.
  2298. if (helpStr) {
  2299. context.write(`${helpStr}\n`);
  2300. }
  2301. });
  2302. return this;
  2303. }
  2304. /**
  2305. * Output help information if help flags specified
  2306. *
  2307. * @param {Array} args - array of options to search for help flags
  2308. * @private
  2309. */
  2310. _outputHelpIfRequested(args) {
  2311. const helpOption = this._getHelpOption();
  2312. const helpRequested = helpOption && args.find((arg) => helpOption.is(arg));
  2313. if (helpRequested) {
  2314. this.outputHelp();
  2315. // (Do not have all displayed text available so only passing placeholder.)
  2316. this._exit(0, 'commander.helpDisplayed', '(outputHelp)');
  2317. }
  2318. }
  2319. }
  2320. /**
  2321. * Scan arguments and increment port number for inspect calls (to avoid conflicts when spawning new command).
  2322. *
  2323. * @param {string[]} args - array of arguments from node.execArgv
  2324. * @returns {string[]}
  2325. * @private
  2326. */
  2327. function incrementNodeInspectorPort(args) {
  2328. // Testing for these options:
  2329. // --inspect[=[host:]port]
  2330. // --inspect-brk[=[host:]port]
  2331. // --inspect-port=[host:]port
  2332. return args.map((arg) => {
  2333. if (!arg.startsWith('--inspect')) {
  2334. return arg;
  2335. }
  2336. let debugOption;
  2337. let debugHost = '127.0.0.1';
  2338. let debugPort = '9229';
  2339. let match;
  2340. if ((match = arg.match(/^(--inspect(-brk)?)$/)) !== null) {
  2341. // e.g. --inspect
  2342. debugOption = match[1];
  2343. } else if (
  2344. (match = arg.match(/^(--inspect(-brk|-port)?)=([^:]+)$/)) !== null
  2345. ) {
  2346. debugOption = match[1];
  2347. if (/^\d+$/.test(match[3])) {
  2348. // e.g. --inspect=1234
  2349. debugPort = match[3];
  2350. } else {
  2351. // e.g. --inspect=localhost
  2352. debugHost = match[3];
  2353. }
  2354. } else if (
  2355. (match = arg.match(/^(--inspect(-brk|-port)?)=([^:]+):(\d+)$/)) !== null
  2356. ) {
  2357. // e.g. --inspect=localhost:1234
  2358. debugOption = match[1];
  2359. debugHost = match[3];
  2360. debugPort = match[4];
  2361. }
  2362. if (debugOption && debugPort !== '0') {
  2363. return `${debugOption}=${debugHost}:${parseInt(debugPort) + 1}`;
  2364. }
  2365. return arg;
  2366. });
  2367. }
  2368. /**
  2369. * @returns {boolean | undefined}
  2370. * @package
  2371. */
  2372. function useColor() {
  2373. // Test for common conventions.
  2374. // NB: the observed behaviour is in combination with how author adds color! For example:
  2375. // - we do not test NODE_DISABLE_COLORS, but util:styletext does
  2376. // - we do test NO_COLOR, but Chalk does not
  2377. //
  2378. // References:
  2379. // https://no-color.org
  2380. // https://bixense.com/clicolors/
  2381. // https://github.com/nodejs/node/blob/0a00217a5f67ef4a22384cfc80eb6dd9a917fdc1/lib/internal/tty.js#L109
  2382. // https://github.com/chalk/supports-color/blob/c214314a14bcb174b12b3014b2b0a8de375029ae/index.js#L33
  2383. // (https://force-color.org recent web page from 2023, does not match major javascript implementations)
  2384. if (
  2385. process.env.NO_COLOR ||
  2386. process.env.FORCE_COLOR === '0' ||
  2387. process.env.FORCE_COLOR === 'false'
  2388. )
  2389. return false;
  2390. if (process.env.FORCE_COLOR || process.env.CLICOLOR_FORCE !== undefined)
  2391. return true;
  2392. return undefined;
  2393. }
  2394. exports.Command = Command;
  2395. exports.useColor = useColor; // exporting for tests