command.js 85 KB

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