index.js 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. 'use strict';
  2. const isPlainObject = require('is-plain-obj');
  3. const arrify = require('arrify');
  4. const kindOf = require('kind-of');
  5. const push = (obj, prop, value) => {
  6. if (!obj[prop]) {
  7. obj[prop] = [];
  8. }
  9. obj[prop].push(value);
  10. };
  11. const insert = (obj, prop, key, value) => {
  12. if (!obj[prop]) {
  13. obj[prop] = {};
  14. }
  15. obj[prop][key] = value;
  16. };
  17. const prettyPrint = output => {
  18. return Array.isArray(output) ?
  19. `[${output.map(prettyPrint).join(', ')}]` :
  20. kindOf(output) === 'string' ? JSON.stringify(output) : output;
  21. };
  22. const resolveType = value => {
  23. if (Array.isArray(value) && value.length > 0) {
  24. const [element] = value;
  25. return `${kindOf(element)}-array`;
  26. }
  27. return kindOf(value);
  28. };
  29. const normalizeExpectedType = (type, defaultValue) => {
  30. const inferredType = type === 'array' ? 'string-array' : type;
  31. if (arrayTypes.includes(inferredType) && Array.isArray(defaultValue) && defaultValue.length === 0) {
  32. return 'array';
  33. }
  34. return inferredType;
  35. };
  36. const passthroughOptions = ['stopEarly', 'unknown', '--'];
  37. const primitiveTypes = ['string', 'boolean', 'number'];
  38. const arrayTypes = primitiveTypes.map(t => `${t}-array`);
  39. const availableTypes = [...primitiveTypes, 'array', ...arrayTypes];
  40. const buildOptions = options => {
  41. options = options || {};
  42. const result = {};
  43. passthroughOptions.forEach(key => {
  44. if (options[key]) {
  45. result[key] = options[key];
  46. }
  47. });
  48. Object.keys(options).forEach(key => {
  49. let value = options[key];
  50. if (key === 'arguments') {
  51. key = '_';
  52. }
  53. // If short form is used
  54. // convert it to long form
  55. // e.g. { 'name': 'string' }
  56. if (typeof value === 'string') {
  57. value = {type: value};
  58. }
  59. if (isPlainObject(value)) {
  60. const props = value;
  61. const {type} = props;
  62. if (type) {
  63. if (!availableTypes.includes(type)) {
  64. throw new TypeError(`Expected type of "${key}" to be one of ${prettyPrint(availableTypes)}, got ${prettyPrint(type)}`);
  65. }
  66. if (arrayTypes.includes(type)) {
  67. const [elementType] = type.split('-');
  68. push(result, 'array', {key, [elementType]: true});
  69. } else {
  70. push(result, type, key);
  71. }
  72. }
  73. if ({}.hasOwnProperty.call(props, 'default')) {
  74. const {default: defaultValue} = props;
  75. const defaultType = resolveType(defaultValue);
  76. const expectedType = normalizeExpectedType(type, defaultValue);
  77. if (expectedType && expectedType !== defaultType) {
  78. throw new TypeError(`Expected "${key}" default value to be of type "${expectedType}", got ${prettyPrint(defaultType)}`);
  79. }
  80. insert(result, 'default', key, defaultValue);
  81. }
  82. arrify(props.alias).forEach(alias => {
  83. insert(result, 'alias', alias, key);
  84. });
  85. }
  86. });
  87. return result;
  88. };
  89. module.exports = buildOptions;
  90. module.exports.default = buildOptions;