index.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632
  1. 'use strict';
  2. Object.defineProperty(exports, '__esModule', { value: true });
  3. var events = require('events');
  4. function toArr(any) {
  5. return any == null ? [] : Array.isArray(any) ? any : [any];
  6. }
  7. function toVal(out, key, val, opts) {
  8. var x, old=out[key], nxt=(
  9. !!~opts.string.indexOf(key) ? (val == null || val === true ? '' : String(val))
  10. : typeof val === 'boolean' ? val
  11. : !!~opts.boolean.indexOf(key) ? (val === 'false' ? false : val === 'true' || (out._.push((x = +val,x * 0 === 0) ? x : val),!!val))
  12. : (x = +val,x * 0 === 0) ? x : val
  13. );
  14. out[key] = old == null ? nxt : (Array.isArray(old) ? old.concat(nxt) : [old, nxt]);
  15. }
  16. function mri2 (args, opts) {
  17. args = args || [];
  18. opts = opts || {};
  19. var k, arr, arg, name, val, out={ _:[] };
  20. var i=0, j=0, idx=0, len=args.length;
  21. const alibi = opts.alias !== void 0;
  22. const strict = opts.unknown !== void 0;
  23. const defaults = opts.default !== void 0;
  24. opts.alias = opts.alias || {};
  25. opts.string = toArr(opts.string);
  26. opts.boolean = toArr(opts.boolean);
  27. if (alibi) {
  28. for (k in opts.alias) {
  29. arr = opts.alias[k] = toArr(opts.alias[k]);
  30. for (i=0; i < arr.length; i++) {
  31. (opts.alias[arr[i]] = arr.concat(k)).splice(i, 1);
  32. }
  33. }
  34. }
  35. for (i=opts.boolean.length; i-- > 0;) {
  36. arr = opts.alias[opts.boolean[i]] || [];
  37. for (j=arr.length; j-- > 0;) opts.boolean.push(arr[j]);
  38. }
  39. for (i=opts.string.length; i-- > 0;) {
  40. arr = opts.alias[opts.string[i]] || [];
  41. for (j=arr.length; j-- > 0;) opts.string.push(arr[j]);
  42. }
  43. if (defaults) {
  44. for (k in opts.default) {
  45. name = typeof opts.default[k];
  46. arr = opts.alias[k] = opts.alias[k] || [];
  47. if (opts[name] !== void 0) {
  48. opts[name].push(k);
  49. for (i=0; i < arr.length; i++) {
  50. opts[name].push(arr[i]);
  51. }
  52. }
  53. }
  54. }
  55. const keys = strict ? Object.keys(opts.alias) : [];
  56. for (i=0; i < len; i++) {
  57. arg = args[i];
  58. if (arg === '--') {
  59. out._ = out._.concat(args.slice(++i));
  60. break;
  61. }
  62. for (j=0; j < arg.length; j++) {
  63. if (arg.charCodeAt(j) !== 45) break; // "-"
  64. }
  65. if (j === 0) {
  66. out._.push(arg);
  67. } else if (arg.substring(j, j + 3) === 'no-') {
  68. name = arg.substring(j + 3);
  69. if (strict && !~keys.indexOf(name)) {
  70. return opts.unknown(arg);
  71. }
  72. out[name] = false;
  73. } else {
  74. for (idx=j+1; idx < arg.length; idx++) {
  75. if (arg.charCodeAt(idx) === 61) break; // "="
  76. }
  77. name = arg.substring(j, idx);
  78. val = arg.substring(++idx) || (i+1 === len || (''+args[i+1]).charCodeAt(0) === 45 || args[++i]);
  79. arr = (j === 2 ? [name] : name);
  80. for (idx=0; idx < arr.length; idx++) {
  81. name = arr[idx];
  82. if (strict && !~keys.indexOf(name)) return opts.unknown('-'.repeat(j) + name);
  83. toVal(out, name, (idx + 1 < arr.length) || val, opts);
  84. }
  85. }
  86. }
  87. if (defaults) {
  88. for (k in opts.default) {
  89. if (out[k] === void 0) {
  90. out[k] = opts.default[k];
  91. }
  92. }
  93. }
  94. if (alibi) {
  95. for (k in out) {
  96. arr = opts.alias[k] || [];
  97. while (arr.length > 0) {
  98. out[arr.shift()] = out[k];
  99. }
  100. }
  101. }
  102. return out;
  103. }
  104. const removeBrackets = (v) => v.replace(/[<[].+/, "").trim();
  105. const findAllBrackets = (v) => {
  106. const ANGLED_BRACKET_RE_GLOBAL = /<([^>]+)>/g;
  107. const SQUARE_BRACKET_RE_GLOBAL = /\[([^\]]+)\]/g;
  108. const res = [];
  109. const parse = (match) => {
  110. let variadic = false;
  111. let value = match[1];
  112. if (value.startsWith("...")) {
  113. value = value.slice(3);
  114. variadic = true;
  115. }
  116. return {
  117. required: match[0].startsWith("<"),
  118. value,
  119. variadic
  120. };
  121. };
  122. let angledMatch;
  123. while (angledMatch = ANGLED_BRACKET_RE_GLOBAL.exec(v)) {
  124. res.push(parse(angledMatch));
  125. }
  126. let squareMatch;
  127. while (squareMatch = SQUARE_BRACKET_RE_GLOBAL.exec(v)) {
  128. res.push(parse(squareMatch));
  129. }
  130. return res;
  131. };
  132. const getMriOptions = (options) => {
  133. const result = {alias: {}, boolean: []};
  134. for (const [index, option] of options.entries()) {
  135. if (option.names.length > 1) {
  136. result.alias[option.names[0]] = option.names.slice(1);
  137. }
  138. if (option.isBoolean) {
  139. if (option.negated) {
  140. const hasStringTypeOption = options.some((o, i) => {
  141. return i !== index && o.names.some((name) => option.names.includes(name)) && typeof o.required === "boolean";
  142. });
  143. if (!hasStringTypeOption) {
  144. result.boolean.push(option.names[0]);
  145. }
  146. } else {
  147. result.boolean.push(option.names[0]);
  148. }
  149. }
  150. }
  151. return result;
  152. };
  153. const findLongest = (arr) => {
  154. return arr.sort((a, b) => {
  155. return a.length > b.length ? -1 : 1;
  156. })[0];
  157. };
  158. const padRight = (str, length) => {
  159. return str.length >= length ? str : `${str}${" ".repeat(length - str.length)}`;
  160. };
  161. const camelcase = (input) => {
  162. return input.replace(/([a-z])-([a-z])/g, (_, p1, p2) => {
  163. return p1 + p2.toUpperCase();
  164. });
  165. };
  166. const setDotProp = (obj, keys, val) => {
  167. let i = 0;
  168. let length = keys.length;
  169. let t = obj;
  170. let x;
  171. for (; i < length; ++i) {
  172. x = t[keys[i]];
  173. t = t[keys[i]] = i === length - 1 ? val : x != null ? x : !!~keys[i + 1].indexOf(".") || !(+keys[i + 1] > -1) ? {} : [];
  174. }
  175. };
  176. const setByType = (obj, transforms) => {
  177. for (const key of Object.keys(transforms)) {
  178. const transform = transforms[key];
  179. if (transform.shouldTransform) {
  180. obj[key] = Array.prototype.concat.call([], obj[key]);
  181. if (typeof transform.transformFunction === "function") {
  182. obj[key] = obj[key].map(transform.transformFunction);
  183. }
  184. }
  185. }
  186. };
  187. const getFileName = (input) => {
  188. const m = /([^\\\/]+)$/.exec(input);
  189. return m ? m[1] : "";
  190. };
  191. const camelcaseOptionName = (name) => {
  192. return name.split(".").map((v, i) => {
  193. return i === 0 ? camelcase(v) : v;
  194. }).join(".");
  195. };
  196. class CACError extends Error {
  197. constructor(message) {
  198. super(message);
  199. this.name = this.constructor.name;
  200. if (typeof Error.captureStackTrace === "function") {
  201. Error.captureStackTrace(this, this.constructor);
  202. } else {
  203. this.stack = new Error(message).stack;
  204. }
  205. }
  206. }
  207. class Option {
  208. constructor(rawName, description, config) {
  209. this.rawName = rawName;
  210. this.description = description;
  211. this.config = Object.assign({}, config);
  212. rawName = rawName.replace(/\.\*/g, "");
  213. this.negated = false;
  214. this.names = removeBrackets(rawName).split(",").map((v) => {
  215. let name = v.trim().replace(/^-{1,2}/, "");
  216. if (name.startsWith("no-")) {
  217. this.negated = true;
  218. name = name.replace(/^no-/, "");
  219. }
  220. return camelcaseOptionName(name);
  221. }).sort((a, b) => a.length > b.length ? 1 : -1);
  222. this.name = this.names[this.names.length - 1];
  223. if (this.negated && this.config.default == null) {
  224. this.config.default = true;
  225. }
  226. if (rawName.includes("<")) {
  227. this.required = true;
  228. } else if (rawName.includes("[")) {
  229. this.required = false;
  230. } else {
  231. this.isBoolean = true;
  232. }
  233. }
  234. }
  235. const processArgs = process.argv;
  236. const platformInfo = `${process.platform}-${process.arch} node-${process.version}`;
  237. class Command {
  238. constructor(rawName, description, config = {}, cli) {
  239. this.rawName = rawName;
  240. this.description = description;
  241. this.config = config;
  242. this.cli = cli;
  243. this.options = [];
  244. this.aliasNames = [];
  245. this.name = removeBrackets(rawName);
  246. this.args = findAllBrackets(rawName);
  247. this.examples = [];
  248. }
  249. usage(text) {
  250. this.usageText = text;
  251. return this;
  252. }
  253. allowUnknownOptions() {
  254. this.config.allowUnknownOptions = true;
  255. return this;
  256. }
  257. ignoreOptionDefaultValue() {
  258. this.config.ignoreOptionDefaultValue = true;
  259. return this;
  260. }
  261. version(version, customFlags = "-v, --version") {
  262. this.versionNumber = version;
  263. this.option(customFlags, "Display version number");
  264. return this;
  265. }
  266. example(example) {
  267. this.examples.push(example);
  268. return this;
  269. }
  270. option(rawName, description, config) {
  271. const option = new Option(rawName, description, config);
  272. this.options.push(option);
  273. return this;
  274. }
  275. alias(name) {
  276. this.aliasNames.push(name);
  277. return this;
  278. }
  279. action(callback) {
  280. this.commandAction = callback;
  281. return this;
  282. }
  283. isMatched(name) {
  284. return this.name === name || this.aliasNames.includes(name);
  285. }
  286. get isDefaultCommand() {
  287. return this.name === "" || this.aliasNames.includes("!");
  288. }
  289. get isGlobalCommand() {
  290. return this instanceof GlobalCommand;
  291. }
  292. hasOption(name) {
  293. name = name.split(".")[0];
  294. return this.options.find((option) => {
  295. return option.names.includes(name);
  296. });
  297. }
  298. outputHelp() {
  299. const {name, commands} = this.cli;
  300. const {
  301. versionNumber,
  302. options: globalOptions,
  303. helpCallback
  304. } = this.cli.globalCommand;
  305. let sections = [
  306. {
  307. body: `${name}${versionNumber ? `/${versionNumber}` : ""}`
  308. }
  309. ];
  310. sections.push({
  311. title: "Usage",
  312. body: ` $ ${name} ${this.usageText || this.rawName}`
  313. });
  314. const showCommands = (this.isGlobalCommand || this.isDefaultCommand) && commands.length > 0;
  315. if (showCommands) {
  316. const longestCommandName = findLongest(commands.map((command) => command.rawName));
  317. sections.push({
  318. title: "Commands",
  319. body: commands.map((command) => {
  320. return ` ${padRight(command.rawName, longestCommandName.length)} ${command.description}`;
  321. }).join("\n")
  322. });
  323. sections.push({
  324. title: `For more info, run any command with the \`--help\` flag`,
  325. body: commands.map((command) => ` $ ${name}${command.name === "" ? "" : ` ${command.name}`} --help`).join("\n")
  326. });
  327. }
  328. let options = this.isGlobalCommand ? globalOptions : [...this.options, ...globalOptions || []];
  329. if (!this.isGlobalCommand && !this.isDefaultCommand) {
  330. options = options.filter((option) => option.name !== "version");
  331. }
  332. if (options.length > 0) {
  333. const longestOptionName = findLongest(options.map((option) => option.rawName));
  334. sections.push({
  335. title: "Options",
  336. body: options.map((option) => {
  337. return ` ${padRight(option.rawName, longestOptionName.length)} ${option.description} ${option.config.default === void 0 ? "" : `(default: ${option.config.default})`}`;
  338. }).join("\n")
  339. });
  340. }
  341. if (this.examples.length > 0) {
  342. sections.push({
  343. title: "Examples",
  344. body: this.examples.map((example) => {
  345. if (typeof example === "function") {
  346. return example(name);
  347. }
  348. return example;
  349. }).join("\n")
  350. });
  351. }
  352. if (helpCallback) {
  353. sections = helpCallback(sections) || sections;
  354. }
  355. console.log(sections.map((section) => {
  356. return section.title ? `${section.title}:
  357. ${section.body}` : section.body;
  358. }).join("\n\n"));
  359. }
  360. outputVersion() {
  361. const {name} = this.cli;
  362. const {versionNumber} = this.cli.globalCommand;
  363. if (versionNumber) {
  364. console.log(`${name}/${versionNumber} ${platformInfo}`);
  365. }
  366. }
  367. checkRequiredArgs() {
  368. const minimalArgsCount = this.args.filter((arg) => arg.required).length;
  369. if (this.cli.args.length < minimalArgsCount) {
  370. throw new CACError(`missing required args for command \`${this.rawName}\``);
  371. }
  372. }
  373. checkUnknownOptions() {
  374. const {options, globalCommand} = this.cli;
  375. if (!this.config.allowUnknownOptions) {
  376. for (const name of Object.keys(options)) {
  377. if (name !== "--" && !this.hasOption(name) && !globalCommand.hasOption(name)) {
  378. throw new CACError(`Unknown option \`${name.length > 1 ? `--${name}` : `-${name}`}\``);
  379. }
  380. }
  381. }
  382. }
  383. checkOptionValue() {
  384. const {options: parsedOptions, globalCommand} = this.cli;
  385. const options = [...globalCommand.options, ...this.options];
  386. for (const option of options) {
  387. const value = parsedOptions[option.name.split(".")[0]];
  388. if (option.required) {
  389. const hasNegated = options.some((o) => o.negated && o.names.includes(option.name));
  390. if (value === true || value === false && !hasNegated) {
  391. throw new CACError(`option \`${option.rawName}\` value is missing`);
  392. }
  393. }
  394. }
  395. }
  396. }
  397. class GlobalCommand extends Command {
  398. constructor(cli) {
  399. super("@@global@@", "", {}, cli);
  400. }
  401. }
  402. var __assign = Object.assign;
  403. class CAC extends events.EventEmitter {
  404. constructor(name = "") {
  405. super();
  406. this.name = name;
  407. this.commands = [];
  408. this.rawArgs = [];
  409. this.args = [];
  410. this.options = {};
  411. this.globalCommand = new GlobalCommand(this);
  412. this.globalCommand.usage("<command> [options]");
  413. }
  414. usage(text) {
  415. this.globalCommand.usage(text);
  416. return this;
  417. }
  418. command(rawName, description, config) {
  419. const command = new Command(rawName, description || "", config, this);
  420. command.globalCommand = this.globalCommand;
  421. this.commands.push(command);
  422. return command;
  423. }
  424. option(rawName, description, config) {
  425. this.globalCommand.option(rawName, description, config);
  426. return this;
  427. }
  428. help(callback) {
  429. this.globalCommand.option("-h, --help", "Display this message");
  430. this.globalCommand.helpCallback = callback;
  431. this.showHelpOnExit = true;
  432. return this;
  433. }
  434. version(version, customFlags = "-v, --version") {
  435. this.globalCommand.version(version, customFlags);
  436. this.showVersionOnExit = true;
  437. return this;
  438. }
  439. example(example) {
  440. this.globalCommand.example(example);
  441. return this;
  442. }
  443. outputHelp() {
  444. if (this.matchedCommand) {
  445. this.matchedCommand.outputHelp();
  446. } else {
  447. this.globalCommand.outputHelp();
  448. }
  449. }
  450. outputVersion() {
  451. this.globalCommand.outputVersion();
  452. }
  453. setParsedInfo({args, options}, matchedCommand, matchedCommandName) {
  454. this.args = args;
  455. this.options = options;
  456. if (matchedCommand) {
  457. this.matchedCommand = matchedCommand;
  458. }
  459. if (matchedCommandName) {
  460. this.matchedCommandName = matchedCommandName;
  461. }
  462. return this;
  463. }
  464. unsetMatchedCommand() {
  465. this.matchedCommand = void 0;
  466. this.matchedCommandName = void 0;
  467. }
  468. parse(argv = processArgs, {
  469. run = true
  470. } = {}) {
  471. this.rawArgs = argv;
  472. if (!this.name) {
  473. this.name = argv[1] ? getFileName(argv[1]) : "cli";
  474. }
  475. let shouldParse = true;
  476. for (const command of this.commands) {
  477. const parsed = this.mri(argv.slice(2), command);
  478. const commandName = parsed.args[0];
  479. if (command.isMatched(commandName)) {
  480. shouldParse = false;
  481. const parsedInfo = __assign(__assign({}, parsed), {
  482. args: parsed.args.slice(1)
  483. });
  484. this.setParsedInfo(parsedInfo, command, commandName);
  485. this.emit(`command:${commandName}`, command);
  486. }
  487. }
  488. if (shouldParse) {
  489. for (const command of this.commands) {
  490. if (command.name === "") {
  491. shouldParse = false;
  492. const parsed = this.mri(argv.slice(2), command);
  493. this.setParsedInfo(parsed, command);
  494. this.emit(`command:!`, command);
  495. }
  496. }
  497. }
  498. if (shouldParse) {
  499. const parsed = this.mri(argv.slice(2));
  500. this.setParsedInfo(parsed);
  501. }
  502. if (this.options.help && this.showHelpOnExit) {
  503. this.outputHelp();
  504. run = false;
  505. this.unsetMatchedCommand();
  506. }
  507. if (this.options.version && this.showVersionOnExit && this.matchedCommandName == null) {
  508. this.outputVersion();
  509. run = false;
  510. this.unsetMatchedCommand();
  511. }
  512. const parsedArgv = {args: this.args, options: this.options};
  513. if (run) {
  514. this.runMatchedCommand();
  515. }
  516. if (!this.matchedCommand && this.args[0]) {
  517. this.emit("command:*");
  518. }
  519. return parsedArgv;
  520. }
  521. mri(argv, command) {
  522. const cliOptions = [
  523. ...this.globalCommand.options,
  524. ...command ? command.options : []
  525. ];
  526. const mriOptions = getMriOptions(cliOptions);
  527. let argsAfterDoubleDashes = [];
  528. const doubleDashesIndex = argv.indexOf("--");
  529. if (doubleDashesIndex > -1) {
  530. argsAfterDoubleDashes = argv.slice(doubleDashesIndex + 1);
  531. argv = argv.slice(0, doubleDashesIndex);
  532. }
  533. let parsed = mri2(argv, mriOptions);
  534. parsed = Object.keys(parsed).reduce((res, name) => {
  535. return __assign(__assign({}, res), {
  536. [camelcaseOptionName(name)]: parsed[name]
  537. });
  538. }, {_: []});
  539. const args = parsed._;
  540. const options = {
  541. "--": argsAfterDoubleDashes
  542. };
  543. const ignoreDefault = command && command.config.ignoreOptionDefaultValue ? command.config.ignoreOptionDefaultValue : this.globalCommand.config.ignoreOptionDefaultValue;
  544. let transforms = Object.create(null);
  545. for (const cliOption of cliOptions) {
  546. if (!ignoreDefault && cliOption.config.default !== void 0) {
  547. for (const name of cliOption.names) {
  548. options[name] = cliOption.config.default;
  549. }
  550. }
  551. if (Array.isArray(cliOption.config.type)) {
  552. if (transforms[cliOption.name] === void 0) {
  553. transforms[cliOption.name] = Object.create(null);
  554. transforms[cliOption.name]["shouldTransform"] = true;
  555. transforms[cliOption.name]["transformFunction"] = cliOption.config.type[0];
  556. }
  557. }
  558. }
  559. for (const key of Object.keys(parsed)) {
  560. if (key !== "_") {
  561. const keys = key.split(".");
  562. setDotProp(options, keys, parsed[key]);
  563. setByType(options, transforms);
  564. }
  565. }
  566. return {
  567. args,
  568. options
  569. };
  570. }
  571. runMatchedCommand() {
  572. const {args, options, matchedCommand: command} = this;
  573. if (!command || !command.commandAction)
  574. return;
  575. command.checkUnknownOptions();
  576. command.checkOptionValue();
  577. command.checkRequiredArgs();
  578. const actionArgs = [];
  579. command.args.forEach((arg, index) => {
  580. if (arg.variadic) {
  581. actionArgs.push(args.slice(index));
  582. } else {
  583. actionArgs.push(args[index]);
  584. }
  585. });
  586. actionArgs.push(options);
  587. return command.commandAction.apply(this, actionArgs);
  588. }
  589. }
  590. const cac = (name = "") => new CAC(name);
  591. if (typeof module !== "undefined") {
  592. module.exports = cac;
  593. Object.assign(module.exports, {
  594. default: cac,
  595. cac,
  596. CAC: CAC,
  597. Command: Command
  598. });
  599. }
  600. exports.CAC = CAC;
  601. exports.Command = Command;
  602. exports.cac = cac;
  603. exports.default = cac;