1 | "use strict";
|
2 |
|
3 | exports.__esModule = true;
|
4 | exports.default = sanitizeConfig;
|
5 |
|
6 | var _fs = _interopRequireDefault(require("fs"));
|
7 |
|
8 | var _path = _interopRequireDefault(require("path"));
|
9 |
|
10 | var _castArray = _interopRequireDefault(require("lodash/castArray"));
|
11 |
|
12 | var _isBoolean = _interopRequireDefault(require("lodash/isBoolean"));
|
13 |
|
14 | var _isFunction = _interopRequireDefault(require("lodash/isFunction"));
|
15 |
|
16 | var _isPlainObject = _interopRequireDefault(require("lodash/isPlainObject"));
|
17 |
|
18 | var _isString = _interopRequireDefault(require("lodash/isString"));
|
19 |
|
20 | var _isFinite = _interopRequireDefault(require("lodash/isFinite"));
|
21 |
|
22 | var _map = _interopRequireDefault(require("lodash/map"));
|
23 |
|
24 | var _listify = _interopRequireDefault(require("listify"));
|
25 |
|
26 | var _kleur = _interopRequireDefault(require("kleur"));
|
27 |
|
28 | var _fastestLevenshtein = require("fastest-levenshtein");
|
29 |
|
30 | var _typeDetect = _interopRequireDefault(require("type-detect"));
|
31 |
|
32 | var _glogg = _interopRequireDefault(require("glogg"));
|
33 |
|
34 | var _qI = require("q-i");
|
35 |
|
36 | var _error = _interopRequireDefault(require("./error"));
|
37 |
|
38 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
39 |
|
40 | const logger = (0, _glogg.default)('rsg');
|
41 | const typeCheckers = {
|
42 | number: _isFinite.default,
|
43 | string: _isString.default,
|
44 | boolean: _isBoolean.default,
|
45 | array: Array.isArray,
|
46 | function: _isFunction.default,
|
47 | object: _isPlainObject.default,
|
48 | 'file path': _isString.default,
|
49 | 'existing file path': _isString.default,
|
50 | 'directory path': _isString.default,
|
51 | 'existing directory path': _isString.default
|
52 | };
|
53 |
|
54 | const typesList = types => (0, _listify.default)(types, {
|
55 | finalWord: 'or'
|
56 | });
|
57 |
|
58 | const shouldBeFile = types => types.some(type => type.includes('file'));
|
59 |
|
60 | const shouldBeDirectory = types => types.some(type => type.includes('directory'));
|
61 |
|
62 | const shouldExist = types => types.some(type => type.includes('existing'));
|
63 |
|
64 | function isDirectory(path) {
|
65 | try {
|
66 | return _fs.default.lstatSync(path).isDirectory();
|
67 | } catch (e) {
|
68 | if (e.code !== 'ENOENT') {
|
69 | throw e;
|
70 | }
|
71 |
|
72 | return false;
|
73 | }
|
74 | }
|
75 |
|
76 |
|
77 |
|
78 |
|
79 |
|
80 |
|
81 |
|
82 |
|
83 |
|
84 |
|
85 | function sanitizeConfig(config, schema, rootDir) {
|
86 |
|
87 | (0, _map.default)(config, (value, keyAny) => {
|
88 | const key = keyAny;
|
89 |
|
90 | if (!schema[key]) {
|
91 |
|
92 | const possibleOptions = Object.keys(schema);
|
93 | const suggestedOption = possibleOptions.reduce((suggestion, option) => {
|
94 | const steps = (0, _fastestLevenshtein.distance)(option, key);
|
95 |
|
96 | if (steps < 2) {
|
97 | return option;
|
98 | }
|
99 |
|
100 | return suggestion;
|
101 | }, '');
|
102 | throw new _error.default(`Unknown config option ${_kleur.default.bold(key)} was found, the value is:\n` + (0, _qI.stringify)(value) + (suggestedOption ? `\n\nDid you mean ${_kleur.default.bold(suggestedOption)}?` : ''), suggestedOption);
|
103 | }
|
104 | });
|
105 |
|
106 | const safeConfig = {};
|
107 | (0, _map.default)(schema, (props, keyAny) => {
|
108 | const key = keyAny;
|
109 | let value = config[key];
|
110 |
|
111 | if (props.process) {
|
112 | value = props.process(value, config, rootDir);
|
113 | }
|
114 |
|
115 | if (value === undefined) {
|
116 |
|
117 | value = props.default;
|
118 |
|
119 | const isRequired = (0, _isFunction.default)(props.required) ? props.required(config) : props.required;
|
120 |
|
121 | if (isRequired) {
|
122 | const message = (0, _isString.default)(isRequired) ? isRequired : `${_kleur.default.bold(key)} config option is required.`;
|
123 | throw new _error.default(message, key);
|
124 | }
|
125 | } else if (props.deprecated) {
|
126 | logger.warn(`${key} config option is deprecated. ${props.deprecated}`);
|
127 | } else if (props.removed) {
|
128 | throw new _error.default(`${_kleur.default.bold(key)} config option was removed. ${props.removed}`);
|
129 | }
|
130 |
|
131 | if (value !== undefined && props.type) {
|
132 | const types = (0, _castArray.default)(props.type);
|
133 |
|
134 | const hasRightType = types.some(type => {
|
135 | if (!typeCheckers[type]) {
|
136 | throw new _error.default(`Wrong type ${_kleur.default.bold(type)} specified for ${_kleur.default.bold(key)} in schema.`);
|
137 | }
|
138 |
|
139 | return typeCheckers[type](value);
|
140 | });
|
141 |
|
142 | if (!hasRightType) {
|
143 | const exampleValue = props.example || props.default;
|
144 | const example = {};
|
145 |
|
146 | if (exampleValue) {
|
147 | example[key] = exampleValue;
|
148 | }
|
149 |
|
150 | const exampleText = exampleValue ? `
|
151 | Example:
|
152 |
|
153 | ${(0, _qI.stringify)(example)}` : '';
|
154 | throw new _error.default(`${_kleur.default.bold(key)} config option should be ${typesList(types)}, received ${(0, _typeDetect.default)(value)}.\n${exampleText}`, key);
|
155 | }
|
156 |
|
157 |
|
158 | if ((0, _isString.default)(value) && (shouldBeFile(types) || shouldBeDirectory(types))) {
|
159 | value = _path.default.resolve(rootDir, value);
|
160 |
|
161 | if (shouldExist(types)) {
|
162 | if (shouldBeFile(types) && !_fs.default.existsSync(value)) {
|
163 | throw new _error.default(`A file specified in ${_kleur.default.bold(key)} config option does not exist:\n${value}`, key);
|
164 | }
|
165 |
|
166 | if (shouldBeDirectory(types) && !isDirectory(value)) {
|
167 | throw new _error.default(`A directory specified in ${_kleur.default.bold(key)} config option does not exist:\n${value}`, key);
|
168 | }
|
169 | }
|
170 | }
|
171 | }
|
172 |
|
173 | safeConfig[keyAny] = value;
|
174 | });
|
175 | return safeConfig;
|
176 | } |
\ | No newline at end of file |