UNPKG

35.6 kBJavaScriptView Raw
1// Copyright Joyent, Inc. and other Node contributors.
2
3var R = typeof Reflect === 'object' ? Reflect : null;
4var ReflectApply = R && typeof R.apply === 'function'
5 ? R.apply
6 : function ReflectApply(target, receiver, args) {
7 return Function.prototype.apply.call(target, receiver, args);
8 };
9
10var ReflectOwnKeys;
11if (R && typeof R.ownKeys === 'function') {
12 ReflectOwnKeys = R.ownKeys;
13} else if (Object.getOwnPropertySymbols) {
14 ReflectOwnKeys = function ReflectOwnKeys(target) {
15 return Object.getOwnPropertyNames(target)
16 .concat(Object.getOwnPropertySymbols(target));
17 };
18} else {
19 ReflectOwnKeys = function ReflectOwnKeys(target) {
20 return Object.getOwnPropertyNames(target);
21 };
22}
23
24function ProcessEmitWarning(warning) {
25 if (console && console.warn) console.warn(warning);
26}
27
28var NumberIsNaN = Number.isNaN || function NumberIsNaN(value) {
29 return value !== value;
30};
31
32function EventEmitter() {
33 EventEmitter.init.call(this);
34}
35var events = EventEmitter;
36
37// Backwards-compat with node 0.10.x
38EventEmitter.EventEmitter = EventEmitter;
39
40EventEmitter.prototype._events = undefined;
41EventEmitter.prototype._eventsCount = 0;
42EventEmitter.prototype._maxListeners = undefined;
43
44// By default EventEmitters will print a warning if more than 10 listeners are
45// added to it. This is a useful default which helps finding memory leaks.
46var defaultMaxListeners = 10;
47
48Object.defineProperty(EventEmitter, 'defaultMaxListeners', {
49 enumerable: true,
50 get: function() {
51 return defaultMaxListeners;
52 },
53 set: function(arg) {
54 if (typeof arg !== 'number' || arg < 0 || NumberIsNaN(arg)) {
55 throw new RangeError('The value of "defaultMaxListeners" is out of range. It must be a non-negative number. Received ' + arg + '.');
56 }
57 defaultMaxListeners = arg;
58 }
59});
60
61EventEmitter.init = function() {
62
63 if (this._events === undefined ||
64 this._events === Object.getPrototypeOf(this)._events) {
65 this._events = Object.create(null);
66 this._eventsCount = 0;
67 }
68
69 this._maxListeners = this._maxListeners || undefined;
70};
71
72// Obviously not all Emitters should be limited to 10. This function allows
73// that to be increased. Set to zero for unlimited.
74EventEmitter.prototype.setMaxListeners = function setMaxListeners(n) {
75 if (typeof n !== 'number' || n < 0 || NumberIsNaN(n)) {
76 throw new RangeError('The value of "n" is out of range. It must be a non-negative number. Received ' + n + '.');
77 }
78 this._maxListeners = n;
79 return this;
80};
81
82function $getMaxListeners(that) {
83 if (that._maxListeners === undefined)
84 return EventEmitter.defaultMaxListeners;
85 return that._maxListeners;
86}
87
88EventEmitter.prototype.getMaxListeners = function getMaxListeners() {
89 return $getMaxListeners(this);
90};
91
92EventEmitter.prototype.emit = function emit(type) {
93 var args = [];
94 for (var i = 1; i < arguments.length; i++) args.push(arguments[i]);
95 var doError = (type === 'error');
96
97 var events = this._events;
98 if (events !== undefined)
99 doError = (doError && events.error === undefined);
100 else if (!doError)
101 return false;
102
103 // If there is no 'error' event listener then throw.
104 if (doError) {
105 var er;
106 if (args.length > 0)
107 er = args[0];
108 if (er instanceof Error) {
109 // Note: The comments on the `throw` lines are intentional, they show
110 // up in Node's output if this results in an unhandled exception.
111 throw er; // Unhandled 'error' event
112 }
113 // At least give some kind of context to the user
114 var err = new Error('Unhandled error.' + (er ? ' (' + er.message + ')' : ''));
115 err.context = er;
116 throw err; // Unhandled 'error' event
117 }
118
119 var handler = events[type];
120
121 if (handler === undefined)
122 return false;
123
124 if (typeof handler === 'function') {
125 ReflectApply(handler, this, args);
126 } else {
127 var len = handler.length;
128 var listeners = arrayClone(handler, len);
129 for (var i = 0; i < len; ++i)
130 ReflectApply(listeners[i], this, args);
131 }
132
133 return true;
134};
135
136function _addListener(target, type, listener, prepend) {
137 var m;
138 var events;
139 var existing;
140
141 if (typeof listener !== 'function') {
142 throw new TypeError('The "listener" argument must be of type Function. Received type ' + typeof listener);
143 }
144
145 events = target._events;
146 if (events === undefined) {
147 events = target._events = Object.create(null);
148 target._eventsCount = 0;
149 } else {
150 // To avoid recursion in the case that type === "newListener"! Before
151 // adding it to the listeners, first emit "newListener".
152 if (events.newListener !== undefined) {
153 target.emit('newListener', type,
154 listener.listener ? listener.listener : listener);
155
156 // Re-assign `events` because a newListener handler could have caused the
157 // this._events to be assigned to a new object
158 events = target._events;
159 }
160 existing = events[type];
161 }
162
163 if (existing === undefined) {
164 // Optimize the case of one listener. Don't need the extra array object.
165 existing = events[type] = listener;
166 ++target._eventsCount;
167 } else {
168 if (typeof existing === 'function') {
169 // Adding the second element, need to change to array.
170 existing = events[type] =
171 prepend ? [listener, existing] : [existing, listener];
172 // If we've already got an array, just append.
173 } else if (prepend) {
174 existing.unshift(listener);
175 } else {
176 existing.push(listener);
177 }
178
179 // Check for listener leak
180 m = $getMaxListeners(target);
181 if (m > 0 && existing.length > m && !existing.warned) {
182 existing.warned = true;
183 // No error code for this since it is a Warning
184 // eslint-disable-next-line no-restricted-syntax
185 var w = new Error('Possible EventEmitter memory leak detected. ' +
186 existing.length + ' ' + String(type) + ' listeners ' +
187 'added. Use emitter.setMaxListeners() to ' +
188 'increase limit');
189 w.name = 'MaxListenersExceededWarning';
190 w.emitter = target;
191 w.type = type;
192 w.count = existing.length;
193 ProcessEmitWarning(w);
194 }
195 }
196
197 return target;
198}
199
200EventEmitter.prototype.addListener = function addListener(type, listener) {
201 return _addListener(this, type, listener, false);
202};
203
204EventEmitter.prototype.on = EventEmitter.prototype.addListener;
205
206EventEmitter.prototype.prependListener =
207 function prependListener(type, listener) {
208 return _addListener(this, type, listener, true);
209 };
210
211function onceWrapper() {
212 var args = [];
213 for (var i = 0; i < arguments.length; i++) args.push(arguments[i]);
214 if (!this.fired) {
215 this.target.removeListener(this.type, this.wrapFn);
216 this.fired = true;
217 ReflectApply(this.listener, this.target, args);
218 }
219}
220
221function _onceWrap(target, type, listener) {
222 var state = { fired: false, wrapFn: undefined, target: target, type: type, listener: listener };
223 var wrapped = onceWrapper.bind(state);
224 wrapped.listener = listener;
225 state.wrapFn = wrapped;
226 return wrapped;
227}
228
229EventEmitter.prototype.once = function once(type, listener) {
230 if (typeof listener !== 'function') {
231 throw new TypeError('The "listener" argument must be of type Function. Received type ' + typeof listener);
232 }
233 this.on(type, _onceWrap(this, type, listener));
234 return this;
235};
236
237EventEmitter.prototype.prependOnceListener =
238 function prependOnceListener(type, listener) {
239 if (typeof listener !== 'function') {
240 throw new TypeError('The "listener" argument must be of type Function. Received type ' + typeof listener);
241 }
242 this.prependListener(type, _onceWrap(this, type, listener));
243 return this;
244 };
245
246// Emits a 'removeListener' event if and only if the listener was removed.
247EventEmitter.prototype.removeListener =
248 function removeListener(type, listener) {
249 var list, events, position, i, originalListener;
250
251 if (typeof listener !== 'function') {
252 throw new TypeError('The "listener" argument must be of type Function. Received type ' + typeof listener);
253 }
254
255 events = this._events;
256 if (events === undefined)
257 return this;
258
259 list = events[type];
260 if (list === undefined)
261 return this;
262
263 if (list === listener || list.listener === listener) {
264 if (--this._eventsCount === 0)
265 this._events = Object.create(null);
266 else {
267 delete events[type];
268 if (events.removeListener)
269 this.emit('removeListener', type, list.listener || listener);
270 }
271 } else if (typeof list !== 'function') {
272 position = -1;
273
274 for (i = list.length - 1; i >= 0; i--) {
275 if (list[i] === listener || list[i].listener === listener) {
276 originalListener = list[i].listener;
277 position = i;
278 break;
279 }
280 }
281
282 if (position < 0)
283 return this;
284
285 if (position === 0)
286 list.shift();
287 else {
288 spliceOne(list, position);
289 }
290
291 if (list.length === 1)
292 events[type] = list[0];
293
294 if (events.removeListener !== undefined)
295 this.emit('removeListener', type, originalListener || listener);
296 }
297
298 return this;
299 };
300
301EventEmitter.prototype.off = EventEmitter.prototype.removeListener;
302
303EventEmitter.prototype.removeAllListeners =
304 function removeAllListeners(type) {
305 var listeners, events, i;
306
307 events = this._events;
308 if (events === undefined)
309 return this;
310
311 // not listening for removeListener, no need to emit
312 if (events.removeListener === undefined) {
313 if (arguments.length === 0) {
314 this._events = Object.create(null);
315 this._eventsCount = 0;
316 } else if (events[type] !== undefined) {
317 if (--this._eventsCount === 0)
318 this._events = Object.create(null);
319 else
320 delete events[type];
321 }
322 return this;
323 }
324
325 // emit removeListener for all listeners on all events
326 if (arguments.length === 0) {
327 var keys = Object.keys(events);
328 var key;
329 for (i = 0; i < keys.length; ++i) {
330 key = keys[i];
331 if (key === 'removeListener') continue;
332 this.removeAllListeners(key);
333 }
334 this.removeAllListeners('removeListener');
335 this._events = Object.create(null);
336 this._eventsCount = 0;
337 return this;
338 }
339
340 listeners = events[type];
341
342 if (typeof listeners === 'function') {
343 this.removeListener(type, listeners);
344 } else if (listeners !== undefined) {
345 // LIFO order
346 for (i = listeners.length - 1; i >= 0; i--) {
347 this.removeListener(type, listeners[i]);
348 }
349 }
350
351 return this;
352 };
353
354function _listeners(target, type, unwrap) {
355 var events = target._events;
356
357 if (events === undefined)
358 return [];
359
360 var evlistener = events[type];
361 if (evlistener === undefined)
362 return [];
363
364 if (typeof evlistener === 'function')
365 return unwrap ? [evlistener.listener || evlistener] : [evlistener];
366
367 return unwrap ?
368 unwrapListeners(evlistener) : arrayClone(evlistener, evlistener.length);
369}
370
371EventEmitter.prototype.listeners = function listeners(type) {
372 return _listeners(this, type, true);
373};
374
375EventEmitter.prototype.rawListeners = function rawListeners(type) {
376 return _listeners(this, type, false);
377};
378
379EventEmitter.listenerCount = function(emitter, type) {
380 if (typeof emitter.listenerCount === 'function') {
381 return emitter.listenerCount(type);
382 } else {
383 return listenerCount.call(emitter, type);
384 }
385};
386
387EventEmitter.prototype.listenerCount = listenerCount;
388function listenerCount(type) {
389 var events = this._events;
390
391 if (events !== undefined) {
392 var evlistener = events[type];
393
394 if (typeof evlistener === 'function') {
395 return 1;
396 } else if (evlistener !== undefined) {
397 return evlistener.length;
398 }
399 }
400
401 return 0;
402}
403
404EventEmitter.prototype.eventNames = function eventNames() {
405 return this._eventsCount > 0 ? ReflectOwnKeys(this._events) : [];
406};
407
408function arrayClone(arr, n) {
409 var copy = new Array(n);
410 for (var i = 0; i < n; ++i)
411 copy[i] = arr[i];
412 return copy;
413}
414
415function spliceOne(list, index) {
416 for (; index + 1 < list.length; index++)
417 list[index] = list[index + 1];
418 list.pop();
419}
420
421function unwrapListeners(arr) {
422 var ret = new Array(arr.length);
423 for (var i = 0; i < ret.length; ++i) {
424 ret[i] = arr[i].listener || arr[i];
425 }
426 return ret;
427}
428var events_1 = events.EventEmitter;
429
430function toArr(any) {
431 return any == null ? [] : Array.isArray(any) ? any : [any];
432}
433
434function toVal(out, key, val, opts) {
435 var x, old=out[key], nxt=(
436 !!~opts.string.indexOf(key) ? (val == null || val === true ? '' : String(val))
437 : typeof val === 'boolean' ? val
438 : !!~opts.boolean.indexOf(key) ? (val === 'false' ? false : val === 'true' || (out._.push((x = +val,x * 0 === 0) ? x : val),!!val))
439 : (x = +val,x * 0 === 0) ? x : val
440 );
441 out[key] = old == null ? nxt : (Array.isArray(old) ? old.concat(nxt) : [old, nxt]);
442}
443
444var lib = function (args, opts) {
445 args = args || [];
446 opts = opts || {};
447
448 var k, arr, arg, name, val, out={ _:[] };
449 var i=0, j=0, idx=0, len=args.length;
450
451 const alibi = opts.alias !== void 0;
452 const strict = opts.unknown !== void 0;
453 const defaults = opts.default !== void 0;
454
455 opts.alias = opts.alias || {};
456 opts.string = toArr(opts.string);
457 opts.boolean = toArr(opts.boolean);
458
459 if (alibi) {
460 for (k in opts.alias) {
461 arr = opts.alias[k] = toArr(opts.alias[k]);
462 for (i=0; i < arr.length; i++) {
463 (opts.alias[arr[i]] = arr.concat(k)).splice(i, 1);
464 }
465 }
466 }
467
468 opts.boolean.forEach(key => {
469 opts.boolean = opts.boolean.concat(opts.alias[key] = opts.alias[key] || []);
470 });
471
472 opts.string.forEach(key => {
473 opts.string = opts.string.concat(opts.alias[key] = opts.alias[key] || []);
474 });
475
476 if (defaults) {
477 for (k in opts.default) {
478 opts.alias[k] = opts.alias[k] || [];
479 (opts[typeof opts.default[k]] || []).push(k);
480 }
481 }
482
483 const keys = strict ? Object.keys(opts.alias) : [];
484
485 for (i=0; i < len; i++) {
486 arg = args[i];
487
488 if (arg === '--') {
489 out._ = out._.concat(args.slice(++i));
490 break;
491 }
492
493 for (j=0; j < arg.length; j++) {
494 if (arg.charCodeAt(j) !== 45) break; // "-"
495 }
496
497 if (j === 0) {
498 out._.push(arg);
499 } else if (arg.substring(j, j + 3) === 'no-') {
500 name = arg.substring(j + 3);
501 if (strict && !~keys.indexOf(name)) {
502 return opts.unknown(arg);
503 }
504 out[name] = false;
505 } else {
506 for (idx=j+1; idx < arg.length; idx++) {
507 if (arg.charCodeAt(idx) === 61) break; // "="
508 }
509
510 name = arg.substring(j, idx);
511 val = arg.substring(++idx) || (i+1 === len || (''+args[i+1]).charCodeAt(0) === 45 || args[++i]);
512 arr = (j === 2 ? [name] : name);
513
514 for (idx=0; idx < arr.length; idx++) {
515 name = arr[idx];
516 if (strict && !~keys.indexOf(name)) return opts.unknown('-'.repeat(j) + name);
517 toVal(out, name, (idx + 1 < arr.length) || val, opts);
518 }
519 }
520 }
521
522 if (defaults) {
523 for (k in opts.default) {
524 if (out[k] === void 0) {
525 out[k] = opts.default[k];
526 }
527 }
528 }
529
530 if (alibi) {
531 for (k in out) {
532 arr = opts.alias[k] || [];
533 while (arr.length > 0) {
534 out[arr.shift()] = out[k];
535 }
536 }
537 }
538
539 return out;
540};
541
542const removeBrackets = (v) => v.replace(/[<[].+/, '').trim();
543const findAllBrackets = (v) => {
544 const ANGLED_BRACKET_RE_GLOBAL = /<([^>]+)>/g;
545 const SQUARE_BRACKET_RE_GLOBAL = /\[([^\]]+)\]/g;
546 const res = [];
547 const parse = (match) => {
548 let variadic = false;
549 let value = match[1];
550 if (value.startsWith('...')) {
551 value = value.slice(3);
552 variadic = true;
553 }
554 return {
555 required: match[0].startsWith('<'),
556 value,
557 variadic
558 };
559 };
560 let angledMatch;
561 while ((angledMatch = ANGLED_BRACKET_RE_GLOBAL.exec(v))) {
562 res.push(parse(angledMatch));
563 }
564 let squareMatch;
565 while ((squareMatch = SQUARE_BRACKET_RE_GLOBAL.exec(v))) {
566 res.push(parse(squareMatch));
567 }
568 return res;
569};
570const getMriOptions = (options) => {
571 const result = { alias: {}, boolean: [] };
572 for (const [index, option] of options.entries()) {
573 // We do not set default values in mri options
574 // Since its type (typeof) will be used to cast parsed arguments.
575 // Which mean `--foo foo` will be parsed as `{foo: true}` if we have `{default:{foo: true}}`
576 // Set alias
577 if (option.names.length > 1) {
578 result.alias[option.names[0]] = option.names.slice(1);
579 }
580 // Set boolean
581 if (option.isBoolean) {
582 if (option.negated) {
583 // For negated option
584 // We only set it to `boolean` type when there's no string-type option with the same name
585 const hasStringTypeOption = options.some((o, i) => {
586 return (i !== index &&
587 o.names.some(name => option.names.includes(name)) &&
588 typeof o.required === 'boolean');
589 });
590 if (!hasStringTypeOption) {
591 result.boolean.push(option.names[0]);
592 }
593 }
594 else {
595 result.boolean.push(option.names[0]);
596 }
597 }
598 }
599 return result;
600};
601const findLongest = (arr) => {
602 return arr.sort((a, b) => {
603 return a.length > b.length ? -1 : 1;
604 })[0];
605};
606const padRight = (str, length) => {
607 return str.length >= length ? str : `${str}${' '.repeat(length - str.length)}`;
608};
609const camelcase = (input) => {
610 return input.replace(/([a-z])-([a-z])/g, (_, p1, p2) => {
611 return p1 + p2.toUpperCase();
612 });
613};
614const setDotProp = (obj, keys, val) => {
615 let i = 0;
616 let length = keys.length;
617 let t = obj;
618 let x;
619 for (; i < length; ++i) {
620 x = t[keys[i]];
621 t = t[keys[i]] =
622 i === length - 1
623 ? val
624 : x != null
625 ? x
626 : !!~keys[i + 1].indexOf('.') || !(+keys[i + 1] > -1)
627 ? {}
628 : [];
629 }
630};
631const setByType = (obj, transforms) => {
632 for (const key of Object.keys(transforms)) {
633 const transform = transforms[key];
634 if (transform.shouldTransform) {
635 obj[key] = Array.prototype.concat.call([], obj[key]);
636 if (typeof transform.transformFunction === 'function') {
637 obj[key] = obj[key].map(transform.transformFunction);
638 }
639 }
640 }
641};
642const getFileName = (input) => {
643 const m = /([^\\\/]+)$/.exec(input);
644 return m ? m[1] : '';
645};
646const camelcaseOptionName = (name) => {
647 // Camelcase the option name
648 // Don't camelcase anything after the dot `.`
649 return name
650 .split('.')
651 .map((v, i) => {
652 return i === 0 ? camelcase(v) : v;
653 })
654 .join('.');
655};
656class CACError extends Error {
657 constructor(message) {
658 super(message);
659 this.name = this.constructor.name;
660 if (typeof Error.captureStackTrace === 'function') {
661 Error.captureStackTrace(this, this.constructor);
662 }
663 else {
664 this.stack = new Error(message).stack;
665 }
666 }
667}
668
669class Option {
670 constructor(rawName, description, config) {
671 this.rawName = rawName;
672 this.description = description;
673 this.config = Object.assign({}, config);
674 // You may use cli.option('--env.* [value]', 'desc') to denote a dot-nested option
675 rawName = rawName.replace(/\.\*/g, '');
676 this.negated = false;
677 this.names = removeBrackets(rawName)
678 .split(',')
679 .map((v) => {
680 let name = v.trim().replace(/^-{1,2}/, '');
681 if (name.startsWith('no-')) {
682 this.negated = true;
683 name = name.replace(/^no-/, '');
684 }
685 return camelcaseOptionName(name);
686 })
687 .sort((a, b) => (a.length > b.length ? 1 : -1)); // Sort names
688 // Use the longest name (last one) as actual option name
689 this.name = this.names[this.names.length - 1];
690 if (this.negated) {
691 this.config.default = true;
692 }
693 if (rawName.includes('<')) {
694 this.required = true;
695 }
696 else if (rawName.includes('[')) {
697 this.required = false;
698 }
699 else {
700 // No arg needed, it's boolean flag
701 this.isBoolean = true;
702 }
703 }
704}
705
706const deno = typeof window !== 'undefined' && window.Deno;
707const denoScriptPath = deno && typeof window !== 'undefined' && window.location.pathname;
708// Adds deno executable and script path to processArgs as "compatibility" layer for node
709// See https://github.com/cacjs/cac/issues/69
710const processArgs = deno
711 ? ['deno', denoScriptPath].concat(Deno.args)
712 : process.argv;
713const platformInfo = deno
714 ? `${Deno.build.os}-${Deno.build.arch} deno-${Deno.version.deno}`
715 : `${process.platform}-${process.arch} node-${process.version}`;
716
717class Command {
718 constructor(rawName, description, config = {}, cli) {
719 this.rawName = rawName;
720 this.description = description;
721 this.config = config;
722 this.cli = cli;
723 this.options = [];
724 this.aliasNames = [];
725 this.name = removeBrackets(rawName);
726 this.args = findAllBrackets(rawName);
727 this.examples = [];
728 }
729 usage(text) {
730 this.usageText = text;
731 return this;
732 }
733 allowUnknownOptions() {
734 this.config.allowUnknownOptions = true;
735 return this;
736 }
737 ignoreOptionDefaultValue() {
738 this.config.ignoreOptionDefaultValue = true;
739 return this;
740 }
741 version(version, customFlags = '-v, --version') {
742 this.versionNumber = version;
743 this.option(customFlags, 'Display version number');
744 return this;
745 }
746 example(example) {
747 this.examples.push(example);
748 return this;
749 }
750 /**
751 * Add a option for this command
752 * @param rawName Raw option name(s)
753 * @param description Option description
754 * @param config Option config
755 */
756 option(rawName, description, config) {
757 const option = new Option(rawName, description, config);
758 this.options.push(option);
759 return this;
760 }
761 alias(name) {
762 this.aliasNames.push(name);
763 return this;
764 }
765 action(callback) {
766 this.commandAction = callback;
767 return this;
768 }
769 /**
770 * Check if a command name is matched by this command
771 * @param name Command name
772 */
773 isMatched(name) {
774 return this.name === name || this.aliasNames.includes(name);
775 }
776 get isDefaultCommand() {
777 return this.name === '' || this.aliasNames.includes('!');
778 }
779 get isGlobalCommand() {
780 return this instanceof GlobalCommand;
781 }
782 /**
783 * Check if an option is registered in this command
784 * @param name Option name
785 */
786 hasOption(name) {
787 name = name.split('.')[0];
788 return this.options.find(option => {
789 return option.names.includes(name);
790 });
791 }
792 outputHelp() {
793 const { name, commands } = this.cli;
794 const { versionNumber, options: globalOptions, helpCallback } = this.cli.globalCommand;
795 const sections = [
796 {
797 body: `${name}${versionNumber ? ` v${versionNumber}` : ''}`
798 }
799 ];
800 sections.push({
801 title: 'Usage',
802 body: ` $ ${name} ${this.usageText || this.rawName}`
803 });
804 const showCommands = (this.isGlobalCommand || this.isDefaultCommand) && commands.length > 0;
805 if (showCommands) {
806 const longestCommandName = findLongest(commands.map(command => command.rawName));
807 sections.push({
808 title: 'Commands',
809 body: commands
810 .map(command => {
811 return ` ${padRight(command.rawName, longestCommandName.length)} ${command.description}`;
812 })
813 .join('\n')
814 });
815 sections.push({
816 title: `For more info, run any command with the \`--help\` flag`,
817 body: commands
818 .map(command => ` $ ${name}${command.name === '' ? '' : ` ${command.name}`} --help`)
819 .join('\n')
820 });
821 }
822 const options = this.isGlobalCommand
823 ? globalOptions
824 : [...this.options, ...(globalOptions || [])];
825 if (options.length > 0) {
826 const longestOptionName = findLongest(options.map(option => option.rawName));
827 sections.push({
828 title: 'Options',
829 body: options
830 .map(option => {
831 return ` ${padRight(option.rawName, longestOptionName.length)} ${option.description} ${option.config.default === undefined
832 ? ''
833 : `(default: ${option.config.default})`}`;
834 })
835 .join('\n')
836 });
837 }
838 if (this.examples.length > 0) {
839 sections.push({
840 title: 'Examples',
841 body: this.examples
842 .map(example => {
843 if (typeof example === 'function') {
844 return example(name);
845 }
846 return example;
847 })
848 .join('\n')
849 });
850 }
851 if (helpCallback) {
852 helpCallback(sections);
853 }
854 console.log(sections
855 .map(section => {
856 return section.title
857 ? `${section.title}:\n${section.body}`
858 : section.body;
859 })
860 .join('\n\n'));
861 }
862 outputVersion() {
863 const { name } = this.cli;
864 const { versionNumber } = this.cli.globalCommand;
865 if (versionNumber) {
866 console.log(`${name}/${versionNumber} ${platformInfo}`);
867 }
868 }
869 checkRequiredArgs() {
870 const minimalArgsCount = this.args.filter(arg => arg.required).length;
871 if (this.cli.args.length < minimalArgsCount) {
872 throw new CACError(`missing required args for command \`${this.rawName}\``);
873 }
874 }
875 /**
876 * Check if the parsed options contain any unknown options
877 *
878 * Exit and output error when true
879 */
880 checkUnknownOptions() {
881 const { options, globalCommand } = this.cli;
882 if (!this.config.allowUnknownOptions) {
883 for (const name of Object.keys(options)) {
884 if (name !== '--' &&
885 !this.hasOption(name) &&
886 !globalCommand.hasOption(name)) {
887 throw new CACError(`Unknown option \`${name.length > 1 ? `--${name}` : `-${name}`}\``);
888 }
889 }
890 }
891 }
892 /**
893 * Check if the required string-type options exist
894 */
895 checkOptionValue() {
896 const { options: parsedOptions, globalCommand } = this.cli;
897 const options = [...globalCommand.options, ...this.options];
898 for (const option of options) {
899 const value = parsedOptions[option.name.split('.')[0]];
900 // Check required option value
901 if (option.required) {
902 const hasNegated = options.some(o => o.negated && o.names.includes(option.name));
903 if (value === true || (value === false && !hasNegated)) {
904 throw new CACError(`option \`${option.rawName}\` value is missing`);
905 }
906 }
907 }
908 }
909}
910class GlobalCommand extends Command {
911 constructor(cli) {
912 super('@@global@@', '', {}, cli);
913 }
914}
915
916class CAC extends events_1 {
917 /**
918 * @param name The program name to display in help and version message
919 */
920 constructor(name = '') {
921 super();
922 this.name = name;
923 this.commands = [];
924 this.globalCommand = new GlobalCommand(this);
925 this.globalCommand.usage('<command> [options]');
926 }
927 /**
928 * Add a global usage text.
929 *
930 * This is not used by sub-commands.
931 */
932 usage(text) {
933 this.globalCommand.usage(text);
934 return this;
935 }
936 /**
937 * Add a sub-command
938 */
939 command(rawName, description, config) {
940 const command = new Command(rawName, description || '', config, this);
941 command.globalCommand = this.globalCommand;
942 this.commands.push(command);
943 return command;
944 }
945 /**
946 * Add a global CLI option.
947 *
948 * Which is also applied to sub-commands.
949 */
950 option(rawName, description, config) {
951 this.globalCommand.option(rawName, description, config);
952 return this;
953 }
954 /**
955 * Show help message when `-h, --help` flags appear.
956 *
957 */
958 help(callback) {
959 this.globalCommand.option('-h, --help', 'Display this message');
960 this.globalCommand.helpCallback = callback;
961 this.showHelpOnExit = true;
962 return this;
963 }
964 /**
965 * Show version number when `-v, --version` flags appear.
966 *
967 */
968 version(version, customFlags = '-v, --version') {
969 this.globalCommand.version(version, customFlags);
970 this.showVersionOnExit = true;
971 return this;
972 }
973 /**
974 * Add a global example.
975 *
976 * This example added here will not be used by sub-commands.
977 */
978 example(example) {
979 this.globalCommand.example(example);
980 return this;
981 }
982 /**
983 * Output the corresponding help message
984 * When a sub-command is matched, output the help message for the command
985 * Otherwise output the global one.
986 *
987 */
988 outputHelp() {
989 if (this.matchedCommand) {
990 this.matchedCommand.outputHelp();
991 }
992 else {
993 this.globalCommand.outputHelp();
994 }
995 }
996 /**
997 * Output the version number.
998 *
999 */
1000 outputVersion() {
1001 this.globalCommand.outputVersion();
1002 }
1003 setParsedInfo({ args, options }, matchedCommand, matchedCommandName) {
1004 this.args = args;
1005 this.options = options;
1006 if (matchedCommand) {
1007 this.matchedCommand = matchedCommand;
1008 }
1009 if (matchedCommandName) {
1010 this.matchedCommandName = matchedCommandName;
1011 }
1012 return this;
1013 }
1014 /**
1015 * Parse argv
1016 */
1017 parse(argv = processArgs, {
1018 /** Whether to run the action for matched command */
1019 run = true } = {}) {
1020 this.rawArgs = argv;
1021 if (!this.name) {
1022 this.name = argv[1] ? getFileName(argv[1]) : 'cli';
1023 }
1024 let shouldParse = true;
1025 // Search sub-commands
1026 for (const command of this.commands) {
1027 const parsed = this.mri(argv.slice(2), command);
1028 const commandName = parsed.args[0];
1029 if (command.isMatched(commandName)) {
1030 shouldParse = false;
1031 const parsedInfo = Object.assign({}, parsed, { args: parsed.args.slice(1) });
1032 this.setParsedInfo(parsedInfo, command, commandName);
1033 this.emit(`command:${commandName}`, command);
1034 }
1035 }
1036 if (shouldParse) {
1037 // Search the default command
1038 for (const command of this.commands) {
1039 if (command.name === '') {
1040 shouldParse = false;
1041 const parsed = this.mri(argv.slice(2), command);
1042 this.setParsedInfo(parsed, command);
1043 this.emit(`command:!`, command);
1044 }
1045 }
1046 }
1047 if (shouldParse) {
1048 const parsed = this.mri(argv.slice(2));
1049 this.setParsedInfo(parsed);
1050 }
1051 if (this.options.help && this.showHelpOnExit) {
1052 this.outputHelp();
1053 run = false;
1054 }
1055 if (this.options.version && this.showVersionOnExit) {
1056 this.outputVersion();
1057 run = false;
1058 }
1059 const parsedArgv = { args: this.args, options: this.options };
1060 if (run) {
1061 this.runMatchedCommand();
1062 }
1063 if (!this.matchedCommand && this.args[0]) {
1064 this.emit('command:*');
1065 }
1066 return parsedArgv;
1067 }
1068 mri(argv,
1069 /** Matched command */ command) {
1070 // All added options
1071 const cliOptions = [
1072 ...this.globalCommand.options,
1073 ...(command ? command.options : [])
1074 ];
1075 const mriOptions = getMriOptions(cliOptions);
1076 // Extract everything after `--` since mri doesn't support it
1077 let argsAfterDoubleDashes = [];
1078 const doubleDashesIndex = argv.indexOf('--');
1079 if (doubleDashesIndex > -1) {
1080 argsAfterDoubleDashes = argv.slice(doubleDashesIndex + 1);
1081 argv = argv.slice(0, doubleDashesIndex);
1082 }
1083 let parsed = lib(argv, mriOptions);
1084 parsed = Object.keys(parsed).reduce((res, name) => {
1085 return Object.assign({}, res, { [camelcaseOptionName(name)]: parsed[name] });
1086 }, { _: [] });
1087 const args = parsed._;
1088 delete parsed._;
1089 const options = {
1090 '--': argsAfterDoubleDashes
1091 };
1092 // Set option default value
1093 const ignoreDefault = command && command.config.ignoreOptionDefaultValue
1094 ? command.config.ignoreOptionDefaultValue
1095 : this.globalCommand.config.ignoreOptionDefaultValue;
1096 let transforms = Object.create(null);
1097 for (const cliOption of cliOptions) {
1098 if (!ignoreDefault && cliOption.config.default !== undefined) {
1099 for (const name of cliOption.names) {
1100 options[name] = cliOption.config.default;
1101 }
1102 }
1103 // If options type is defined
1104 if (Array.isArray(cliOption.config.type)) {
1105 if (transforms[cliOption.name] === undefined) {
1106 transforms[cliOption.name] = Object.create(null);
1107 transforms[cliOption.name]['shouldTransform'] = true;
1108 transforms[cliOption.name]['transformFunction'] =
1109 cliOption.config.type[0];
1110 }
1111 }
1112 }
1113 // Set dot nested option values
1114 for (const key of Object.keys(parsed)) {
1115 const keys = key.split('.');
1116 setDotProp(options, keys, parsed[key]);
1117 setByType(options, transforms);
1118 }
1119 return {
1120 args,
1121 options
1122 };
1123 }
1124 runMatchedCommand() {
1125 const { args, options, matchedCommand: command } = this;
1126 if (!command || !command.commandAction)
1127 return;
1128 command.checkUnknownOptions();
1129 command.checkOptionValue();
1130 command.checkRequiredArgs();
1131 const actionArgs = [];
1132 command.args.forEach((arg, index) => {
1133 if (arg.variadic) {
1134 actionArgs.push(args.slice(index));
1135 }
1136 else {
1137 actionArgs.push(args[index]);
1138 }
1139 });
1140 actionArgs.push(options);
1141 return command.commandAction.apply(this, actionArgs);
1142 }
1143}
1144
1145/**
1146 * @param name The program name to display in help and version message
1147 */
1148const cac = (name = '') => new CAC(name);
1149if (typeof module !== 'undefined') {
1150 module.exports = cac;
1151 module.exports.default = cac;
1152 module.exports.cac = cac;
1153}
1154
1155export default cac;
1156export { cac };